每当构造一个对象时,构造函数是否应始终将其保留在"初始化"州?
例如,如果一个Image类有两个构造函数,其中一个接受文件路径字符串,另一个不接受任何参数,那么后者将图像对象置于无效状态是不好的做法?
假设编写了类来处理这两种状态。
我问这个是因为我觉得在很多情况下几乎有必要使用默认构造。特别是当一个对象是一个类的成员并且你想在该类的构造函数中初始化它时。
编辑:我知道会员初始化列表。我发现了一些情况,我想在它所持有的类的构造函数中构造对象,而不是之前。虽然,我知道这可能比任何其他替代方案更危险。
答案 0 :(得分:13)
这一切归结为"有效状态的定义":如果类的方法在路径为空时处理状态,则具有空路径的状态是有效状态,并且绝对可以接受。
但是,从编码的角度来看,这可能不是最佳的,因为您可能需要为路径添加多个检查才能生效。您通常可以通过实施State Pattern来管理复杂性。
我发现在很多情况下几乎有必要使用默认构造。特别是当一个对象是一个类的成员并且你想在该类的构造函数中初始化它时。
只要在初始化列表中构造依赖项,就不需要默认构造函数来初始化它所属的类的构造函数中的对象。
答案 1 :(得分:3)
你的最后一行:
我问这个是因为我觉得在很多情况下几乎有必要使用默认构造。特别是当一个对象是一个类的成员并且你想在该类的构造函数中初始化它时。
意味着您没有使用成员初始化列表。在这种情况下,您不需要默认构造函数。例如:
class Member {
public:
Member(std::string str) { std::cout << str << std::endl; }
};
class Foo {
public:
Foo() : member_("Foo") {}
private:
Member member_;
}
此外,您的问题标题和身体冲突以及术语有点模糊。在构造时,通常最好将对象保持在有效且可用的状态。有时第二个方面(可用)不太必要,许多解决方案都需要它。此外,在C ++ 11中,从对象移动必须使其处于有效状态,但不一定(并且在许多情况下不应该)将其保持在可用状态。
编辑:要解决您在构造函数中工作的问题,请考虑将工作移动到Member类的静态成员或者私有(静态或非静态)函数中。拥有班级:
class Member {
public:
Member(std::string str) { std::cout << str << std::endl; }
};
class Foo {
public:
Foo() : member_(CreateFoo()) {}
private:
Member CreateMember() {
std::string str;
std::cin >> str;
return Member(str);
}
Member member_;
};
然而,这种方法的一个危险是,如果使用非静态成员函数进行创建,则初始化顺序可能很重要。静态函数更安全,但您可能希望传递一些其他相关的成员信息。请记住,初始化是按类中的成员声明的顺序完成的,而不是初始化列表声明顺序。
答案 2 :(得分:2)
是的,它应该始终有效。但是,通常没有很好地定义使对象有效的原因。至少,该对象应该可以在不崩溃的情况下使用。但是,这并不意味着可以对对象执行所有操作,但至少应该有一个。在许多情况下,这只是assignment
来自另一个来源(例如std容器迭代器)和destruction
(这一个是强制性的,即使在移动之后)。但是,在任何类型的状态下,对象支持的操作越多,它就越不容易出错。
这通常是一种权衡。如果你可以逃脱只有所有操作都有效的状态的对象,那肯定很棒。然而,这些情况很少见,如果你必须通过箍来实现目标,通常更容易添加和记录其某些功能的前提条件。在某些情况下,您甚至可以拆分界面以区分进行此权衡的功能和不进行权衡的功能。一个流行的例子是std::vector
,你需要有足够的元素作为使用operator[]
的前提条件。另一方面,at()
函数仍然有效,但抛出异常。
答案 3 :(得分:2)
首先,让我们定义“有效状态”究竟是什么:它是一个对象可以完成其工作的状态。
例如,如果我们正在编写一个管理文件并让我们编写和读取文件的类,则有效状态(遵循我们的定义)可能是一个状态,其中对象持有正确的oppened文件并准备读取/写在上面。
但考虑其他情况:左移的状态是什么?
File::File( File&& other )
{
_file_handle = other._file_handle;
other._file_handle = nullptr; //Whats this state?
}
这是一个文件对象尚未准备好在文件上写入/读取的状态,但是可以初始化。也就是说,准备初始化状态。
现在考虑使用复制和交换习惯用法的上述ctor的替代实现:
File::File() :
_file_handle{ nullptr }
{}
File::File( File&& other ) : File() //Set the object to a ready to initialice state
{
using std::swap; //Enable ADL
swap( *this , other );
}
这里我们使用默认的ctor来将对象置于准备初始化状态,并且只是将传递的rvalue与此对象交换,从而产生与第一个实现完全相同的行为。
正如我们上面所看到的,有一件事就是准备好工作状态,一个状态,对象已经准备好做什么了,而另一件完全不同的事情是准备就绪初始化状态:对象尚未准备好工作的状态,但已准备好初始化并设置为可以工作。。
我对你的问题的回答是:一个对象不是处于有效状态(它不能完全准备好使用),但是如果它还没有准备好使用它应该准备初始化然后准备好了工作。
答案 4 :(得分:1)
通常,是的。我已经看到了一些好的反例,但它们非常罕见。