【问题标题】:Constructor called on an already created object构造函数调用了已创建的对象
【发布时间】:2011-06-29 15:57:33
【问题描述】:

如果我在已经构造的对象或结构上调用构造函数, 它会分配新空间,还是只使用现有空间?那么第一个对象分配是否更占用资源?像这样:

struct F {
    int a,b,c,d;
    F(int _a, int _b) {a = _a; b = _b}; 
    void a(int _a, int _b) {a = _a; b = _b};
};  

//first constructor call
F f = F(5, 6);

//second constructor call on an already constructed object
f = F(7, 8);

//third constructor call on an already constructed object
f(7, 8);

//is the constructor call more res. intesive, than the call to a function which does the same? 
f.a(9, 0)

构造函数调用是否比调用具有相同功能的函数更占用资源 (void a(...))?

当我在已创建的对象上调用构造函数时,是否会调用析构函数?

【问题讨论】:

  • 看来你真的不明白语法,所以很难回答你的问题 - 也许让它编译,我们可以帮助你
  • 抱歉,我纠正的 f/F 确实有点搞砸了;)

标签: c++ memory constructor allocation


【解决方案1】:

首先,[c] 标签是不合适的,因为构造函数是 C++ 独有的特性。我假设您提供的代码 sn-p 实际上是 C++,而不是 C 的一些奇怪的方言。C++ 和 C 是不同的语言;不要将你的问题标记为两者,因为你会得到不同的答案。

第二,你的构造函数定义是错误的。构造函数必须与类本身具有完全相同的名称。所以f() 应该是F()。是的,区分大小写在 C++ 中很重要!我假设这就是你对其余代码 sn-p 的意思。 OP 只是打错了。

在我解释其余代码的作用之前,您必须了解 C++ 中的所有类(和结构)都有special member functions,如果您不提供它们,编译器会自动生成它们。也就是你的代码sn-p基本上是一样的:

struct F
{
    F(int _a, int _b) {a = _a; b = _b};  // constructor
    ~F() {}                              // destructor
    F(const F& rhs)                      // copy constructor
        : a(rhs.a)
        , b(rhs.b)
        , c(rhs.c)
        , d(rhs.d)
    {}
    F& operator=(const F& a)             // copy assignment operator
    {
        a = rhs.a;
        b = rhs.b;
        c = rhs.c;
        d = rhs.d;
        return *this;
    }

    void a(int _a, int _b) {a = _a; b = _b};   // one of your functions

    int a;
    int b;
    int c;
    int d;
};

如果您没有定义复制构造函数、复制赋值运算符或析构函数,编译器将为您生成它们。此外,如果您不提供其他构造函数,编译器将生成一个默认构造函数。 F 类没有默认构造函数,因为已经有一个(非复制)构造函数接受两个参数。

复制构造函数和复制赋值运算符的默认实现只是简单地复制每个数据成员。

之所以存在特殊成员函数,是因为 C++ 推广了将原始类型复制到用户定义对象的概念。考虑一下:

int a = 42;
int b = 13;
b = a;

对于像ints 这样的原始类型,您可以像这样复制它的值。 C++ 将复制语义推广到对象,因此您可以这样做:

F f(10, 20); // calls first constructor
F g(30, 40); // calls first constructor
g = f;       // calls g's copy-assignment operator.

现在您可以看到这如何应用于您的代码:

F f = F(5,6); 

上面的行构造了一个临时的F 对象,然后通过复制构造函数将该临时对象复制到f。临时的F 对象随后被破坏。

f = F(7,8);

上面的行构造了另一个临时的F 对象,然后通过复制赋值操作符将这个临时对象赋值给f。临时的F 对象随后被破坏。原始的f 对象没有被破坏。

f.a(9,0)   

上面的行是对名为f 的对象的普通函数调用。

对于您的代码 sn-p,假设编译器不会优化临时对象(实际上它们通常会这样做),那么调用函数 a 会“占用较少的资源”,因为在这种情况下不会生成临时对象。但是,对于您的第一个构造函数调用,您可以这样做:

F f(5,6); // Constructor called; no temporaries are made

了解构造函数的用途:它们用于创建对象。如果你已经有一个对象,那么你就不需要调用构造函数了。

正如我多次推荐的,请拿起good C++ book阅读。特殊成员函数及其作用是 C++ 的基础。

【讨论】:

  • 错字。在第二个构造函数调用语句中将f = f(7,8);更改为f = F(7,8)
  • @Mahesh:感谢您的关注。
  • 另外,在F 定义中应该有一个接受两个整数参数的构造函数。否则无法编译。
  • 好的,所以 F f = F(5,6);比 F f(5,6) 更耗费资源;因为它涉及复制运算符,但我可以建议如果我调用 f(5,6);比它与 f.a(5,6) 的资源密集度相同吗?谢谢
  • @Peter Lapisu:不完全是。 F f(5, 6) 使堆栈分配足够的空间来存储您的 f 对象(即 f 对象被推入堆栈)。函数调用不需要推送任何内容,因为 f 对象已经存在(尽管如果没有内联堆栈帧将被推送到堆栈上)。对于您提供的特定代码 sn-p,构造函数和函数 a() 做的事情几乎相同,因此没有太大区别。但有可能构造函数和a() 在内存使用方面做完全不同的事情。
【解决方案2】:

你的类 F 没有构造函数,只有方法 f。因此,任何时候都不会调用构造函数。

【讨论】:

    【解决方案3】:

    你的程序有语法错误,修改你的sn-p-

    struct F {
    
        int a,b,c,d;
    
        F(int _a, int _b) {a = _a; b = _b}; 
    
        void a(int _a, int _b) {a = _a; b = _b};
    
    };
    
    F f = F(5,6); // Here the default copy constructor gets called.
                  // i.e., F( const F& obj ) ;
    
    f = F(7,8);   // A temporary is created is assigned to `f` through default copy assignment operator.
                  // i.e., F& operator=( const F& obj );
    

    当构造的对象超出范围(即对象的生命周期结束时)时,将调用析构函数。

    【讨论】:

      【解决方案4】:

      (在您发布的代码中,您在构造函数中使用了小写的 f 而不是大写的 F,我认为这是一个错字)

      你的问题很有趣,因为你问的问题和你写的代码不一样。在代码中,你写了

      f = F(7, 8);
      

      这确实调用了 F 构造函数,但不在现有的 f 对象上。相反,这会创建一个临时 F 对象,通过调用 tue 构造函数以 7 和 8 作为参数进行初始化,然后使用赋值运算符将现有的 f 变量设置为等于这个新对象。因此,构造函数不会在对象上调用两次;相反,它是在这里调用的任务。更一般地说,如果您曾经为现有对象分配新值,则将使用赋值运算符而不是构造函数来进行复制。这将重用对象的现有内存,尽管它可能会触发辅助内存分配和释放。

      没有安全的方法可以两次调用对象的构造函数。如果你真的想调用已经创建的对象的构造函数,你可以使用placement new:

      F f(3, 5);
      new (&f) F(7, 9);
      

      这是不安全的,因为它绕过了析构函数通常会执行的典型资源清理并盲目地覆盖现有元素,因此在实践中几乎从未这样做过。我提到它主要是为了好奇和完整性。 :-)

      【讨论】:

      • +1 因为你在问题的关键点上是正确的!在已经存在的对象上调用构造函数以重新初始化它似乎是一个很常见的错误(我试图做同样的事情),尤其是在初学者中!
      • 我仍然有疑问。假设您有一个在另一个类的类构造函数中构造的成员对象,并且在其中一个成员函数中您想用新值完全重建它(使用它的构造函数),如果您没有重载@987654323,您将如何做到这一点@运算符?
      • @Matteo,我相信f.F::F(8, 9);会通过调用已有对象的构造函数来重构对象。
      【解决方案5】:

      我忽略了 OP 中的实现,直接进入 QA:

      如果我在已经构造的对象/结构上调用构造函数,它会分配新空间

      不,对象不会被重新分配。将调用构造函数的实现(例如函数调用)。

      那么第一个对象分配是否更占用资源?

      你通常不应该这样做,除非你正在实现一个集合(例如vector)。

      但是,要回答这个问题:是的,第二个将需要更少的说明。编译器生成简单的指令来设置分配(如果在堆栈或其他地方)。因此,自动创建对象确实有几个阶段,而您正在绕过其中的一些阶段。但说真的:不要认为这是一种优化——只有在绝对必要时才手动调用构造函数/析构函数(再次考虑集合类型)。

      是构造函数调用更多res。 intesive,而不是对执行相同操作的函数的调用 (void a(...)) ?

      潜在地,编译器的努力也有影响。记住,构造函数和析构函数调用层次结构中每个类的实现,所以......如果你的类层次结构不平凡,一个单一的函数很可能会更快。

      当我在已创建的对象上调用构造函数时,是否会调用析构函数?

      如果您以这种方式显式构造一个对象,那么答案是否定的。适当地配对显式调用是您的工作。你不能错误地实现它,它和 UB 一样好。顺序始终为construct->destruct

      只是为了清楚使用的语法:

      // explicit ctor
      new(ptr) T(ctorArgument);
      // explicit dtor
      ptr->~T();
      

      【讨论】:

        猜你喜欢
        • 2019-07-25
        • 2013-09-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-15
        • 1970-01-01
        相关资源
        最近更新 更多