我知道我们可以使用范围解析运算符(即className::className()
)在C ++中显式调用类的构造函数。我想知道我究竟需要在哪里打电话。
答案 0 :(得分:44)
您有时也明确使用构造函数来构建临时文件。例如,如果你有一个带有构造函数的类:
class Foo
{
Foo(char* c, int i);
};
和一个功能
void Bar(Foo foo);
但你没有Foo,你可以做到
Bar(Foo("hello", 5));
这就像演员。实际上,如果你有一个只接受一个参数的构造函数,那么C ++编译器将使用该构造函数来执行隐式转换。
在已存在的对象上调用构造函数不是合法的。也就是说,你做不到
Foo foo;
foo.Foo(); // compile error!
无论你做什么。但是你可以在不分配内存的情况下调用构造函数 - 这就是 placement new 的用途。
char buffer[sizeof(Foo)]; // a bit of memory
Foo* foo = new(buffer) Foo(); // construct a Foo inside buffer
你给了一些新的内存,它在那个位置构造对象而不是分配新的内存。这种用法被认为是邪恶的,在大多数类型的代码中很少见,但在嵌入式和数据结构代码中很常见。
例如,std::vector::push_back
使用此技术来调用复制构造函数。这样,它只需要做一个副本,而不是创建一个空对象并使用赋值运算符。
答案 1 :(得分:13)
通常,在子类构造函数中需要一些参数:
class BaseClass
{
public:
BaseClass( const std::string& name ) : m_name( name ) { }
const std::string& getName() const { return m_name; }
private:
const std::string m_name;
//...
};
class DerivedClass : public BaseClass
{
public:
DerivedClass( const std::string& name ) : BaseClass( name ) { }
// ...
};
class TestClass :
{
public:
TestClass( int testValue ); //...
};
class UniqueTestClass
: public BaseClass
, public TestClass
{
public:
UniqueTestClass()
: BaseClass( "UniqueTest" )
, TestClass( 42 )
{ }
// ...
};
......例如。
除此之外,我没有看到实用程序。我只是在我太小的时候才用其他代码调用构造函数来知道我在做什么......
答案 2 :(得分:3)
我认为编译器错误C2585的错误消息给出了你需要在构造函数上实际使用范围解析运算符的最佳理由,并且它与查理的答案有关:
基于多重继承从类或结构类型转换。如果类型多次继承相同的基类,则转换函数或运算符必须使用范围解析(::)来指定要在转换中使用的继承类。
假设您有BaseClass,BaseClassA和BaseClassB都继承BaseClass,然后DerivedClass继承BaseClassA和BaseClassB。
如果您正在进行转换或运算符重载以将DerivedClass转换为BaseClassA或BaseClassB,则需要确定在转换中使用哪个构造函数(我正在考虑类似于复制构造函数IIRC)。
答案 3 :(得分:2)
通常,您不直接调用构造函数。 new运算符为您调用它,或者子类调用父类的构造函数。在C ++中,基类被保证在派生类的构造函数启动之前完全构造。
您唯一一次直接调用构造函数的情况是极少数情况下您在不使用new的情况下管理内存。即便如此,你也不应该这样做。相反,您应该使用operator new的放置形式。
答案 4 :(得分:0)
我认为你通常不会将它用于构造函数,至少不会像你描述的那样。但是,如果在不同的命名空间中有两个类,则需要它。例如,要指定这两个组成类之间的差异,Xml::Element
和Chemistry::Element
。
通常,类的名称与范围解析运算符一起使用,以在继承的类的父级上调用函数。因此,如果你有一个继承自Animal的类Dog,并且这两个类以不同方式定义函数Eat(),则可能存在一种情况,当你想在名为“someDog”的Dog对象上使用eat版本的eat时。我的C ++语法有点生疏,但我认为在这种情况下你会说someDog.Animal::Eat()
。
答案 5 :(得分:0)
有一些有效的用例,您希望公开类构造函数。例如,如果您希望使用竞技场分配器进行自己的内存管理,则需要一个由分配和对象初始化组成的两阶段构造。
我采用的方法类似于许多其他语言。我只是将我的构造代码放在众所周知的公共方法( Construct(), init(),类似的东西)中,并在需要时直接调用它们。
您可以创建与构造函数匹配的这些方法的重载;你的常规构造函数只是调用它们。在代码中添加大量注释,以警告其他人您正在执行此操作,以便他们不会在错误的位置添加重要的构造代码。
请记住,无论使用哪种构造重载,只有一种析构函数方法,因此要使析构函数对未初始化的成员具有鲁棒性。
我建议不要尝试编写可以重新初始化的初始化程序。由于未初始化的内存与实际持有的实际数据,很难说你正在看一个只有垃圾的对象。
最困难的问题来自具有虚拟方法的类。在这种情况下,编译器通常将vtable函数表指针作为类的开头的隐藏字段插入。你可以手动初始化这个指针,但你基本上依赖于编译器的特定行为,它可能会让你的同事看着你搞笑。
在许多方面打破了新的安置;在构造/销毁数组是一个案例,所以我倾向于不使用它。
答案 6 :(得分:0)
考虑以下计划。
template<class T>
double GetAverage(T tArray[], int nElements)
{
T tSum = T(); // tSum = 0
for (int nIndex = 0; nIndex < nElements; ++nIndex)
{
tSum += tArray[nIndex];
}
// Whatever type of T is, convert to double
return double(tSum) / nElements;
}
这将显式调用默认构造函数来初始化变量。