在 c + + 中不使用 new 调用构造函数

我经常看到人们使用 C + + 创建对象

Thing myThing("asdf");

而不是这样:

Thing myThing = Thing("asdf");

这似乎是有效的(使用 gcc) ,至少在没有涉及到模板的情况下。我现在的问题是,第一行是否正确,如果正确,我应该使用它吗?

176468 次浏览

这两句话实际上都是正确的,只是做的事情有些微妙的不同。

第一行通过调用格式为 Thing(const char*)的构造函数在堆栈上创建一个新对象。

第二个比较复杂

  1. 使用构造函数 Thing(const char*)创建类型为 Thing的对象
  2. 使用构造函数 Thing(const Thing&)创建类型为 Thing的对象
  3. 对步骤 # 1中创建的对象调用 ~Thing()

很简单,两行都在堆栈上创建对象,而不是像“ new”那样在堆上创建对象。第二行实际上涉及到对复制建构子的第二次调用,因此应该避免使用它(它也需要按照注释中的说明进行更正)。您应该尽可能多地为小对象使用堆栈,因为它更快,但是如果您的对象要比堆栈帧存活更长时间,那么这显然是错误的选择。

我猜你第二句话的意思是:

Thing *thing = new Thing("uiae");

这将是创建新的 充满活力对象(动态绑定和多态性所必需的)并将其地址存储到指针的标准方法。代码执行 JaredPar 描述的操作,即创建两个对象(一个传递 const char*,另一个传递 const Thing&) ,然后对第一个对象(const char*对象)调用析构函数(~Thing())。

相比之下,这一点:

Thing thing("uiae");

创建一个静态对象,该对象在退出当前范围时自动销毁。

理想情况下,编译器会优化第二个,但它不是必需的。第一种是最好的方法。但是,在 C + + 中理解堆栈和堆之间的区别是非常关键的,因为您必须管理自己的堆内存。

编译器可以将第二种形式优化为第一种形式,但它不必这样做。

#include <iostream>


class A
{
public:
A() { std::cerr << "Empty constructor" << std::endl; }
A(const A&) { std::cerr << "Copy constructor" << std::endl; }
A(const char* str) { std::cerr << "char constructor: " << str << std::endl; }
~A() { std::cerr << "destructor" << std::endl; }
};


void direct()
{
std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
A a(__FUNCTION__);
static_cast<void>(a); // avoid warnings about unused variables
}


void assignment()
{
std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
A a = A(__FUNCTION__);
static_cast<void>(a); // avoid warnings about unused variables
}


void prove_copy_constructor_is_called()
{
std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
A a(__FUNCTION__);
A b = a;
static_cast<void>(b); // avoid warnings about unused variables
}


int main()
{
direct();
assignment();
prove_copy_constructor_is_called();
return 0;
}

Gcc 4.4的输出:

TEST: direct
char constructor: direct
destructor


TEST: assignment
char constructor: assignment
destructor


TEST: prove_copy_constructor_is_called
char constructor: prove_copy_constructor_is_called
Copy constructor
destructor
destructor

我对它进行了一些处理,当构造函数没有参数时,语法似乎变得很奇怪。让我举个例子:

#include <iostream>


using namespace std;


class Thing
{
public:
Thing();
};


Thing::Thing()
{
cout << "Hi" << endl;
}


int main()
{
//Thing myThing(); // Does not work
Thing myThing; // Works


}

所以只要写 Thing myThing w/o 括号就可以调用构造函数,而 Thing myThing ()会让编译器创建一个函数指针之类的东西!

附在 JaredPar答案后

带有临时对象的1-通常的 ctor,2-函数类似的 ctor。

使用不同的编译器在 http://melpon.org/wandbox/的某个地方编译这个源代码

// turn off rvo for clang, gcc with '-fno-elide-constructors'


#include <stdio.h>
class Thing {
public:
Thing(const char*){puts(__FUNCTION__ );}
Thing(const Thing&){puts(__FUNCTION__ );}
~Thing(){puts(__FUNCTION__);}
};
int main(int /*argc*/, const char** /*argv*/) {
Thing myThing = Thing("asdf");
}

你会看到结果的。

源自 ISO/IEC148822003-10-15

8.5第12部分

您的第一个、第二个构造称为直接初始化

12.1第13部分

可以使用函数表示法类型转换(5.2.3)来创建 类型的新对象。[注意: 语法看起来像一个显式调用 ... 以这种方式创建的对象是未命名的。 [注: 12.2描述了临时对象的生存期。][注: 显式的构造函数调用不产生左值,参见3.10. ]


从哪里阅读 RVO:

12个特殊成员函数/12.8复制类对象/第15部分

当满足某些条件时,允许实现省略 类对象的复制结构,如果复制建构子 和/或对象的析构函数具有 副作用

从注释中关闭编译器标志以查看这种复制行为)