我知道在C++中我们可以使用域解析运算符显式地调用一个类的构造函数,即 className::className()
。我想知道在什么情况下需要进行这样的调用。
我知道在C++中我们可以使用域解析运算符显式地调用一个类的构造函数,即 className::className()
。我想知道在什么情况下需要进行这样的调用。
有时你需要显式地使用构造函数来创建临时对象。例如,如果你有一个带有构造函数的类:
class Foo
{
Foo(char* c, int i);
};
以及一个函数
void Bar(Foo foo);
但是如果你没有Foo,你可以这样做
Bar(Foo("hello", 5));
这类似于类型转换。如果你有一个只接受一个参数的构造函数,C++编译器将使用该构造函数来执行隐式类型转换。
在已存在的对象上调用构造函数是不合法的。也就是说,你不能这样做:
Foo foo;
foo.Foo(); // compile error!
无论你做什么。但是你可以调用构造函数而不分配内存 - 这就是 放置new 的作用。
char buffer[sizeof(Foo)]; // a bit of memory
Foo* foo = new(buffer) Foo(); // construct a Foo inside buffer
你提供一段新的内存,它在那里构造对象,而不是分配新的内存。这种用法被认为是有害的,在大多数类型的代码中很少见,但在嵌入式和数据结构代码中很常见。<typename>(ctor-arg列表)
,严谨来说并不同于<typename>::<typename>(ctor-arg列表)
。这个语法只是让它看起来像你在调用构造函数。事实上,你永远不会像调用函数一样“调用”构造函数。 - Lightness Races in Orbit<typename>::<typename> <var-name>(ctor-arg list)
时,实际上发生了什么? - user1599559通常情况下,在需要一些参数的子类构造函数中:
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 )
{ }
// ...
};
...例如。
除此之外,我看不到其实用之处。我只是在我还太年轻而不知道自己在做什么的时候,在其他代码中调用了构造函数...
通常情况下,您不会直接调用构造函数。new操作符会为您调用它,或者子类会调用父类的构造函数。在C++中,基类在派生类的构造函数开始之前保证完全构造。
唯一需要直接调用构造函数的情况是极其罕见的,即在不使用new管理内存的情况下。即使在这种情况下,您也不应该这样做。相反,您应该使用operator new的放置形式。
请考虑以下程序。
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;
}
这将显式调用默认构造函数来初始化变量。
有一些有效的用例需要公开类的构造函数。例如,如果您希望使用竞技场分配器进行自己的内存管理,则需要两阶段构造,包括分配和对象初始化。
我采取的方法类似于许多其他语言。我只需将构造代码放在众所周知的公共方法(例如Construct()、init()等)中,并在需要时直接调用它们。
您可以创建与您的构造函数匹配的这些方法的重载;您的常规构造函数仅调用它们。在代码中放置大型注释,以警告其他人您正在执行此操作,以免他们在错误的位置添加重要的构造代码。
请记住,无论使用哪个构造函数重载,都只有一个析构函数方法,因此要使您的析构函数能够处理未初始化的成员。
我建议不要尝试编写可以重新初始化的初始化程序。很难区分您是否正在查看由于未初始化的内存而仅具有垃圾数据的对象还是实际持有真实数据的对象。
最困难的问题出现在具有虚方法的类中。在这种情况下,编译器会在类的开头插入vtable函数表指针作为隐藏字段。您可以手动初始化此指针,但基本上取决于编译器特定的行为,并且很可能会让您的同事看着你发呆。我认为你通常不会在构造函数中以你描述的方式使用它。然而,如果你有两个不同命名空间中的类,你将需要它。例如,为了区分这两个虚构的类Xml :: Element
和Chemistry :: Element
。
通常,类的名称与作用域解析运算符一起使用,调用继承类的父类上的函数。因此,如果你有一个从Animal继承的Dog类,并且这些类都以不同的方式定义Eat()函数,那么可能有一种情况,当你想在名为"someDog"的Dog对象上使用Animal版本的eat时。我的C++语法有点生疏,但我认为在这种情况下,你会说someDog.Animal :: Eat()
。