有没有一种方法可以使用类实例指针调用构造函数?

17

我们可以通过类指针显式调用析构函数,为什么不能调用构造函数?有任何想法吗?

#include <iostream>

class Con {
public:
    Con( int x ) : x( x ) {

    }

private:
    int x;
};

int main() {
    Con* c = new Con( 1 );
    //c->Con( 2 ); //illegal
    c->~Con(); // ok!
    delete c;
}

谢谢。


1
c->~Con(); 可能没问题,但是下一行的 delete c; 不行,因为它会尝试再次调用同一个析构函数。 - Cubbi
1
你正在通过_object指针调用析构函数,而不是类指针。 - xtofl
正如xtofl所说,您正在使用对象指针调用析构函数。但是如果您没有使用构造函数,则没有指向构造函数的对象。 - Sam
@xtofl:谢谢,是我不对!应该是对象指针。 - roxrook
@Cubbi:谢谢。我在添加c->~Con()时有些粗心。 - roxrook
7个回答

20
您实际上可以调用它,只是语法不是调用成员方法(其中析构函数是一个特殊情况),因此不使用成员访问运算符进行调用。相反,您必须采用放置 new 的语法:
Con c;
c.~Con();        // destroy, now c is not a Con anymore
new (&c) Con();  // recreate, now c is a Con again

作为一个特例,在C++0x提案中,实际上在其中一个代码示例中使用,提供了一种重用union为不同类型的方法,如果Union包含非POD元素:
union U {
   int i;
   float f;
   std::string s;
};

int main() {
   U u;
   new (&u.s) std::string( "foo" );
   u.s.~string();
   u.i = 5;
}

}


有点警告:我注意到这可能会覆盖在构造函数中没有设置的现有数据。也就是说,如果你分配指针、设置不应该由构造函数设置的变量,并调用放置新变量,那么本来不应该改变的变量可能已经被更改了。我发现__memset_avx2_unaligned_erms覆盖了一些本不该被覆盖的东西。 - philn

16

不行,你不能这样做。

Con* c = new Con( 1 );
//c->Con( 2 ); //illegal

您已经在new表达式中调用了构造函数。

当您拥有一个有效的Con*类型指针时,您已经创建了一个对象。在“已构造”对象上调用构造函数甚至没有意义。那么为什么C++会允许这样做呢?


3
这可能很好,但不正确:并不是你无法调用构造函数,而是执行构造函数的语法很特殊,即定位new运算符new (c) Con(1); - David Rodríguez - dribeas
@David:哪里不对了?你没有在对象上调用构造函数。你正在“构造”另一个对象,而该对象占用的内存与c相同。这与new表达式本身没有太大区别。 - Nawaz
3
假设问题可能是不精确的,让我们用另一种方式表达它:您可以在已构造的对象上执行构造函数,如果对象存在,则会产生未定义行为,就像在已销毁的对象上调用析构函数一样是错误的:Con c; c.~Con(); c.~Con();。如果您使用“调用析构函数”来表示“销毁”,那么同样地,您可以说“调用构造函数”来表示“构造”,这可以通过使用new(包括放置new)完成。 - David Rodríguez - dribeas
我理解这个问题不在于“应该做什么”或者“是否正确”,而是在于“语法是否允许”,在这个意义上,略微不同的语法确实允许你手动执行指向类型的指针上的构造函数,就像你可以手动执行析构函数一样。在这两种情况下,语法都有好的用途和坏的用途(在C++0x草案中有一个例子,告诉你在处理可能具有非平凡构造函数/析构函数的联合时手动调用析构函数并在原地重建)。 - David Rodríguez - dribeas

3

如果你不把构造函数和析构函数看作可调用的函数,你会更容易理解。你不能直接调用它们,只能构造或销毁一个对象。在构造对象时,构造函数体被执行;同样,在销毁对象时,析构函数体被执行。

因此,你可以在堆栈上构造对象。

YourClass variable(constructor_arguments);

当变量超出作用域时,它将自动被销毁。

你也可以在堆上创建对象。

YourClass * ptr = new YourClass(parameters);

为了销毁这样的对象,您需要使用运算符delete
delete ptr;

你也可以在自己提供的一些内存中构造对象(很少需要)
char * pool = new char[sizeof(YourClass)]
YourClass *ptr = new(pool) YourClass(parameters);

你需要显式地销毁这样一个对象,语法看起来像函数调用,但实际上是对象销毁。
ptr->~YourClass();

在这行代码之后,你的对象就不存在了。对它进行任何操作都是未定义的行为。而且你仍然需要管理为该对象分配的内存。

delete[] pool;

所以,你的问题是“为什么我可以显式销毁我有指针指向的对象,但我却无法构造它”?你无法这样做,因为它已经被构造了。
你也可以阅读C++ FAQ Lite explanation

1

只有在对象正在构造时才能调用构造函数,因此它被命名为这样。一旦对象被构造,我看不出为什么你会想在同一个对象上再次调用它。如果您想要在那时做些事情,您需要调用该类中定义的函数。


你实际上可以调用构造函数,只是不能使用成员访问语义,而是要使用放置new(即void*版本的放置new的整体目的是提供在原地创建对象的方法,也就是在一块内存上执行构造函数)。 - David Rodríguez - dribeas
@David,谢谢!我还不够高级,还不知道那个 :) - Tony The Lion

1
构造函数的目的是在对象创建时被调用。仅此而已。如果您有一个引用计数器来跟踪对象数量,允许构造函数作为函数调用将会破坏计数器。
如果您想重新初始化或重置对象,可以添加一个名为Reset()或Initialize()的函数,并从构造函数中调用它。然后,您也可以从对象指针调用Reset()或Initialize()。

0

当然你可以从对象实例中调用它,看看这个代码:

 #include <iostream>
class foo{
      public :
      int foo_var;
      foo(){
            std::cout<<"foo_var = "<<foo_var<<std::endl;
            }
      };
int main()
{
    foo * f = new foo();
    // set foo_var
    f->foo_var = 10;
    // call constructor by passing object instanse
    foo * f1 = new(f) foo;
    std::cout<<(f==f1)<<std::endl;
    system("pause");
    return 0;
}

第二个输出是"foo_var = 10",所以它有效。第三个输出是"true",那么(f==f1)意味着构造函数没有分配新的内存(operator new会分配内存,但我们传递了一个指针,所以它不会再分配)。这是一个非常有用的练习:

#include <iostream>
template <class t>
class smart_pointer{
      t * p; // the real normal pointer
      public :
      smart_pointer()
      {
          p = (t*) malloc(sizeof(t)); // allocate memory 
          try{ 
          new (p) t; // call the constructor 
          } catch (...)  // if it throws any exception
          {
                  free(p); // free p , dont use delete , because it will
                  // call the destroctor calling the destructor will cause 
                  // freeing a not allocated memory that causes a crash
                  throw; // throw the exception what ever it was
          }

      }
      ~smart_pointer(){delete p;}
      t operator = (t val) // assigment operator
      {
               *p = val;
               return val;
      }
      operator t(){ // type casting operator , usually for std::cout
               return *p;
               }
      };
int main()
{
    smart_pointer<int> x;
    x = 10;
    std::cout<<x<<std::endl;
    system("pause");
    return 0;
}

建议阅读《Effective C++》、《More Effective C++》和《Effective Modern C++》这些书籍。


0

不,你不能按照你所解释的方式调用类的构造函数,因为c并没有指向con类型的有效对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接