在没有 new 的情况下在 C++ 中调用构造函数

Posted

技术标签:

【中文标题】在没有 new 的情况下在 C++ 中调用构造函数【英文标题】:Calling constructors in c++ without new 【发布时间】:2010-04-27 16:09:58 【问题描述】:

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

Thing myThing("asdf");

而不是这个:

Thing myThing = Thing("asdf");

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

【问题讨论】:

任何一种形式都没有新的。 第二种形式将使用复制构造函数,所以不,它们不等价。 我玩了一下,当模板与无参数构造函数一起使用时,第一种方法有时似乎会失败.. 哦,我为此获得了“好问题”徽章,真可惜! 【参考方案1】:

这两行实际上都是正确的,但做的事情却略有不同。

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

第二个有点复杂。它基本上执行以下操作

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

【讨论】:

我猜这些类型的操作已经过优化,因此在性能方面没有显着差异。 我认为您的步骤不太正确。 Thing myThing = Thing(...) 不使用赋值运算符,它仍然像 Thing myThing(Thing(...)) 一样是复制构造的,并且不涉及默认构造的 Thing(编辑:随后更正了帖子) 所以你可以说第二行是不正确的,因为它无缘无故地浪费了资源。当然,创建第一个实例可能是为了某些副作用而故意的,但这甚至更糟(在风格上)。 不,@Jared,不能保证。但即使编译器选择执行该优化,复制构造函数仍然需要可访问(即,不受保护或私有),即使它没有实现或调用。 即使复制构造函数有副作用,似乎也可以省略复制 - 请参阅我的回答:***.com/questions/2722879/…【参考方案2】:

我假设您实际上是指第二行:

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

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

相比之下,这个:

Thing thing("uiae");

创建一个静态对象,在退出当前作用域时自动销毁。

【讨论】:

不幸的是,这确实是创建新动态对象而不是使用 auto_ptr、unique_ptr 或相关的最常用方法。 OP 的问题是正确的,这个答案完全涉及另一个问题(参见@JaredPar 的答案)【参考方案3】:

编译器可以很好地将第二种形式优化为第一种形式,但不是必须的。

#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

【讨论】:

静态强制转换为 void 的目的是什么? @Stephen 避免警告未使用的变量。【参考方案4】:

很简单,这两行都在堆栈上创建对象,而不是像“new”那样在堆上创建对象。第二行实际上涉及对复制构造函数的第二次调用,因此应该避免(它还需要按照 cmets 中的指示进行更正)。您应该尽可能将堆栈用于小对象,因为它更快,但是如果您的对象的生存时间比堆栈帧长,那么这显然是错误的选择。

【讨论】:

对于那些不熟悉在堆栈上实例化对象与在堆上实例化对象之间的区别(即使用 new 和不使用 new) , here's a good thread.【参考方案5】:

我玩了一下,当构造函数不带参数时,语法似乎变得很奇怪。举个例子吧:

#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 实际上会调用构造函数,而 Thing myThing() 使编译器成为您想要创建函数指针或其他东西的东西??!!

【讨论】:

这是 C++ 中众所周知的句法歧义。当您编写“int rand()”时,编译器无法知道您的意思是“创建一个 int 并默认初始化它”还是“声明函数 rand”。规则是尽可能选择后者。 这就是most vexing parse。【参考方案6】:

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

【讨论】:

编译器能否保证复制构造函数没有副作用(如I/O)? @Stephen - 复制构造函数是否执行 I/O 并不重要 - 请参阅我的答案 ***.com/questions/2722879/… 好的,我明白了,允许编译器将第二种形式转换为第一种形式,从而避免调用复制构造函数。【参考方案7】:

附加到 JaredPar 答案

1-通常的ctor,带有临时对象的2nd-function-like-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/IEC 14882 2003-10-15

8.5,第 12 部分

您的第 1、2 个构造称为直接初始化

12.1,第 13 部分

功能符号类型转换 (5.2.3) 可用于创建 其类型的新对象。 [注意:语法看起来像显式调用 的构造函数。 ] ...以这种方式创建的对象是未命名的。 [注:12.2 描述了临时对象的生命周期。 ] [笔记: 显式构造函数调用不会产生左值,见 3.10。 ]


在哪里了解 RVO:

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

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

使用注释中的编译器标志将其关闭以查看此类复制行为)

【讨论】:

以上是关于在没有 new 的情况下在 C++ 中调用构造函数的主要内容,如果未能解决你的问题,请参考以下文章

Jest - Angular 9 - 类构造函数 SomeComponentClass 不能在没有'new'的情况下被调用

c++(在类中)执行buf=new char[i];delete []buf; 为啥没有调用构造和析构函数?

为啥我不能在 C++ 中用 new 调用参数化构造函数?

c++中,析构函数和delete各有啥作用啊

C++中复制构造函数被调用的三种情况

内存泄漏简析