给出一个person类的以下简单构造函数声明:
Person(const string& _firstName, const string& _lastName, const string& _id);
哪种方式被认为是优雅且不易出错以确保给定参数有效? 假设我想确保PERSON类型的对象包含空字符串,即使只有一个给定的参数无效。 我提出了这个解决方案:
Person::Person(const string& _firstName, const string& _lastName, const string& _id) {
if (!isValidName(_firstName) || !isValidName(_lastName)) {
firstName = ""; lastName = ""; id = "";
throw InvalidNameException();
}
if (!isValidID(_id)) {
firstName = ""; lastName = ""; id = "";
throw InvalidIDException();
}
firstName = _firstName;
lastName = _lastName;
id = _id;
}
现在ctor对我来说太过臃肿了,我想过写一个init方法,但我真的不喜欢那个解决方案。
很想听听一些建议。
答案 0 :(得分:3)
我建议在初始化列表中初始化您的成员变量,然后检查它们是否有效。如果他们不这样做,就抛出异常。
Person::Person(const string& _firstName, const string& _lastName, const string& _id) :
firstName( _firstName ),
lastName( _lastName ),
id( _id )
{
if (!isValidName( firstName ) || !isValidName( lastName ) || !isValidID( id ) ) {
throw InvalidNameException();
}
在进入构造函数体之前,会初始化您的成员变量。问题中的代码会将成员变量初始化为空,然后在将它们重新分配时再次初始化它们。
通过抛出异常,无论如何都不认为该对象是被构造的,因此您不需要清除"清除"成员变量。在抛出异常之前成功构造的任何成员变量都将调用其析构函数。请注意,如果您的对象抛出异常,则抛出它将不会调用其析构函数(因为它从未完全创建)。
答案 1 :(得分:3)
为什么不包含isValidName
和isValidId
函数
抛出或返回字符串的函数:
std::string
checkedName( std::string const& name )
{
if ( !isValidName( name ) ) {
throw InvalidNameException();
}
return name;
}
std::string
checkedId( std::string const& id )
{
if ( !isValidID( id ) ) {
throw InvalidIDException();
}
return id;
}
然后:
Person::Person( std::string const& firstName,
std::string const& lastName,
std::string const& id )
: myFirstName( checkedName( firstName ) )
, myLastName( checkedName( lastName ) )
, myId( checkedId( id ) )
{
}
或者(但在我看来可读性较差),你可以使用 三元运算符:
Person::Person( std::string const& firstName,
std::string const& lastName,
std::string const& id )
: myFirstName( isValidName( firstName )
? firstName
: throw InvalidNameException() )
, myLastName( isValidName( lastName )
? lastName
: throw InvalidNameException() )
, myId( isValidID( id )
? id
: throw InvalidIDException() )
{
}
无论哪种方式,您都不要进入构造函数的主体 除非字符串有效。
答案 2 :(得分:0)
那几乎没有臃肿。这只是几行。但是在构造函数中执行错误检查总是有问题的。
最好的方法可能就是抛出异常,并让调用者决定如何处理错误。
我确实无法想象为什么在确定参数无效后必须将_firstName
和_lastName
设置为空字符串。
Person::Person(const string& _firstName, const string& _lastName, const string& _id) {
if (!isValidName(_firstName) || !isValidName(_lastName))
throw InvalidNameException();
if (!isValidID(_id))
throw InvalidIDException();
firstName = _firstName;
lastName = _lastName;
id = _id;
}
这根本不是臃肿。
答案 3 :(得分:0)
如果从构造函数中抛出,则认为您的对象从未构造过。在这种情况下,没有必要将值设置为空字符串,因为在从构造函数中抛出异常后,以某种方式尝试访问成员时,它将是未定义的行为。
Person::Person(const string& _firstName, const string& _lastName, const string& _id)
: firstName(_firstName), lastName(_lastName), id(_id) {
if (!isValidName(firstName) || !isValidName(lastName)) throw InvalidNameException();
if (!isValidID(_id)) throw InvalidIDException();
}
答案 4 :(得分:0)
我认为您需要将您的疑虑分开。
first_name
没有last_name
?first_name
的情况下拥有last_name
和id
是否有意义?first_name
,last_name
或id
是否有意义?我相信所有问题的答案都是“不”。出于这个原因,我认为这三个实体属于一个名为Identifier
(或类似)的抽象概念。情况就是这样,因为您需要提供的检查和保证并非易事。
该课程可能如下所示:
class Identifier {
public:
static void ValidateName(const std::string& name) {
if(name.size() <= 0) {
std::stringstream ss;
ss<<"Invalid name: "<<name<<" (expected non-empty string)";
throw std::invalid_argument(ss.str());
}
}
static void ValidateId(const std::string& id) {
if(id.size() != 10) {
std::stringstream ss;
ss<<"Invalid id: "<<id<<" (expected string of length 10)";
throw std::invalid_argument(ss.str());
}
}
Identifier(const std::string& first, const std::string& last, const std::string& id)
: m_first(first),
m_last(last),
m_id(id) {
Identifier::ValidateName(m_first);
Identifier::ValidateName(m_last);
Identifier::ValidateId(m_id);
}
const std::string& first_name() const {
return m_first;
}
const std::string& last_name() const {
return m_last;
}
const std::string& id() const {
return m_id;
}
private:
std::string m_first;
std::string m_last;
std::string m_id;
};
考虑到类的设计方式,不可能创建无效的Identifier
:要么通过所有测试,要么首先创建Identifier
。
此外,请注意,Identifier
创建后无法使其无效;这保证了如果Identifier
的实例存在,它将在整个存在期间有效。
鉴于该保证,您现在可以通过构造函数创建Person
实例,该构造函数采用Identifier
;构造函数永远不会抛出,也不需要进行任何检查。 Person
类可能如下所示:
class Person {
public:
Person(const Identifier& identifier) noexcept(true)
: m_identifier(identifier) { }
const std::string& first_name() const {
return m_identifier.first_name();
}
const std::string& last_name() const {
return m_identifier.last_name();
}
const std::string& id() const {
return m_identifier.id();
}
private:
Identifier m_identifier;
};
以这种方式分离您的顾虑:
Identifier
课程,而无需触及Person
课程:您的课程可以独立发展Identifier
,而无需重复检查。例如:您可以创建Identifier
并将它们引入数据库。最终代码是:
#include<iostream>
#include<sstream>
#include<stdexcept>
class Identifier {
public:
static void ValidateName(const std::string& name) {
if(name.size() <= 0) {
std::stringstream ss;
ss<<"Invalid name: "<<name<<" (expected non-empty string)";
throw std::invalid_argument(ss.str());
}
}
static void ValidateId(const std::string& id) {
if(id.size() != 10) {
std::stringstream ss;
ss<<"Invalid id: "<<id<<" (expected string of length 10)";
throw std::invalid_argument(ss.str());
}
}
Identifier(const std::string& first, const std::string& last, const std::string& id)
: m_first(first),
m_last(last),
m_id(id) {
Identifier::ValidateName(m_first);
Identifier::ValidateName(m_last);
Identifier::ValidateId(m_id);
}
const std::string& first_name() const {
return m_first;
}
const std::string& last_name() const {
return m_last;
}
const std::string& id() const {
return m_id;
}
private:
std::string m_first;
std::string m_last;
std::string m_id;
};
class Person {
public:
Person(const Identifier& identifier) noexcept(true)
: m_identifier(identifier) { }
const std::string& first_name() const {
return m_identifier.first_name();
}
const std::string& last_name() const {
return m_identifier.last_name();
}
const std::string& id() const {
return m_identifier.id();
}
private:
Identifier m_identifier;
};
int main() {
Identifier id("John", "Doe", "1234567890");
Person p(id); // cannot throw because id has already been
// constructed
std::cout<<p.last_name()<<", "<<p.first_name()<<" Id: "<<p.id()<<std::endl;
try {
Identifier id2("Sue", "Smith", "126789");
Person p2(id2);
std::cout<<p2.first_name()<<std::endl;
} catch(const std::exception &e) {
std::cout<<e.what()<<std::endl;
}
try {
Identifier id3("", "Smith", "126789");
Person p3(id3);
std::cout<<p3.first_name()<<std::endl;
} catch(const std::exception &e) {
std::cout<<e.what()<<std::endl;
}
return 0;
}
可以使用以下命令(OS X 10.7.4上的GCC 4.8.1)编译
g++ validated_names.cpp -std=c++11 -Wall -Wextra
并产生以下输出:
./a.out
Doe, John Id: 1234567890
Invalid id: 126789 (expected string of length 10)
Invalid name: (expected non-empty string)