使用new(this)重用构造函数

时间:2010-03-22 20:30:02

标签: c++

最近出现在我作为助教的课堂上。我们正在教学生如何用c ++编写复制构造函数,而最初教过java的学生问你是否可以从另一个构建函数调用。我知道这个问题的答案是否定的,因为他们在课堂上使用了迂腐的旗帜作为他们的代码,旧的标准对此没有支持。我在Stackoverflow和其他网站上发现了使用new (this)伪造此内容的建议,如下所示

class MyClass
{
    private:
        int * storedValue;
    public:
        MyClass(int initialValue = 0)
        {
            storedValue = new int(initialValue);
        }

        ~ MyClass()
        {
            delete storedValue;
        }

        MyClass(const MyClass &b)
        {
            new (this) MyClass(*(b.storedValue));
        }

        int value() {
            return *storedValue;
        }
};

这是非常简单的代码,显然不会通过重用构造函数来保存任何代码,但它仅仅是例如。

我的问题是,这是否符合标准,如果有任何边缘情况应该考虑,以防止这是声音代码?

编辑:我应该注意到这对我来说似乎非常危险,但更多的是从我不太了解它而不是知道它如何变坏。我只是想确保如果学生被问到我可以指导他们为什么能够或不应该这样做。为了所有实际目的,我已经向他们建议使用共享初始化方法。这更像是一个教学问题,而不是一个实际项目。

7 个答案:

答案 0 :(得分:16)

C ++ 0x将引入语法以允许构造函数调用其他构造函数。

在此之前,new(this)适用于某些案例,但不是全部。特别是,一旦在构造函数中,您的基类已经完全构造。重构via new(this)重新调用基础构造函数而不调用基本析构函数,所以如果基类没有期待这种hackery,那么就会出现问题 - 而且它们可能不是。

清晰的例子:

class Base
{
public:
   char *ptr;
   MyFile file;
   std::vector vect;
   Base()
   {
       ptr = new char[1000];
       file.open("some_file");
   }
   ~Base()
   {
       delete [] ptr;
       file.close();
   }
};

class Derived : Base
{
    Derived(Foo foo)
    {
    }
    Derived(Bar bar)
    {
       printf(ptr...);  // ptr in base is already valid
       new (this) Derived(bar.foo); // ptr re-allocated, original not deleted
       //Base.file opened twice, not closed
       // vect is who-knows-what
       // etc
    }
}

或正如他们所说''欢闹随之而来'

答案 1 :(得分:11)

成员和基类将在进入构造函数体之前初始化,然后在调用第二个构造函数时再次初始化。通常,这将导致内存泄漏并可能导致未定义的行为。

所以答案是“不,这不是声音代码”。

答案 2 :(得分:5)

以下是C ++常见问题解答中有关它的内容,在question中,“类的一个构造函数可以调用同一个类的另一个构造函数来初始化此对象吗?”:

  

BTW不要试图通过新的安置来实现这一目标。有些人认为他们可以在new(this) Foo(x, int(x)+7)的正文中说Foo::Foo(char)。然而,这是坏的,坏的,坏的。请不要写信告诉我似乎来处理特定版本的特定编译器;这不好。构造函数在幕后做了一堆小小的神奇事情,但是那些不好的技术会在那些部分构造的位上进行。说不。

答案 3 :(得分:3)

除非你试图调用父的构造函数,否则我建议使用私有初始化方法。没有理由不能在构造函数中调用共享初始化程序。

答案 4 :(得分:1)

如果你有这样的构造函数,这不起作用:

class MyClass {
public:
    MyClass( const std::string & PathToFile )
    : m_File( PathToFile.c_str( ) )
    {
    }
private:
    std::ifstream m_File;
}

原始参数无法恢复,因此您无法从复制构造函数中调用该构造函数。

答案 5 :(得分:1)

由于编写了确切的代码,它应该可以工作 - 尽管我无法想象为什么要编写这样的代码。特别是,它取决于所有指针仅用于引用单个int的事实。既然如此,他们为什么不直接在对象中放入一个int ,而不是使用指针并动态分配int?简而言之,他们所拥有的是漫长而低效的,但与以下方面并无显着差异:

class MyClass {
    int v;
public:
    MyClass(int init) : v(init) {}
    int value() { return v; }
};

不幸的是,一旦你试图从指针中获得一些实际用途(例如,在不同对象中分配不同数量的内存),他们使用放置新退出工作的“技巧” - 它完全取决于每个object正在分配完全相同的内存量。既然你在每个中都限制了完全相同的分配,为什么要把这个分配放在堆中而不是让它成为对象本身的一部分呢?

说实话,的情况才有意义。然而,我能想到的唯一一个就是分配很大你在堆空间比堆栈空间多的环境中运行。

代码有效,但只在相当狭窄的特定情况下才有用。作为如何做事的一个例子,它并没有让我觉得这样做。

答案 6 :(得分:0)

在我看来,如果你知道你在做什么,即使在派生类的构造函数中也可以安全地使用new(this)。您只需确保您的基类具有虚拟构造函数(对于其基类,在链中一直都是相同的)。例如:

#include <stdio.h>
#include <new>

struct Dummy {};

struct print
{
    print(const char *message)                    { fputs(message, stdout); }
    print(const char *format, int arg1)           { printf(format, arg1); }
    print(const char *format, int arg1, int arg2) { printf(format, arg1, arg2); }
};
struct print2 : public print
{
    print2(const char *message)                    : print(message) {}
    print2(const char *format, int arg1)           : print(format, arg1) {}
    print2(const char *format, int arg1, int arg2) : print(format, arg1, arg2) {}
};

class foo : public print
{
    int *n;
public:
    foo(Dummy) : print("foo::foo(Dummy) {}\n") {}
    foo() : print("foo::foo() : n(new int) {}\n"), n(new int) {}
    foo(int n) : print("foo::foo(int n=%d) : n(new int(n)) {}\n", n), n(new int(n)) {}
    int Get() const { return *n; }
    ~foo()
    {
        printf("foo::~foo() { delete n; }\n");
        delete n;
    }
};

class bar : public print2, public foo
{
public:
    bar(int x, int y) : print2("bar::bar(int x=%d, int y=%d) : foo(x*y) {}\n", x, y), foo(x*y) {}
    bar(int n) : print2("bar::bar(int n=%d) : foo(Dummy()) { new(this) bar(n, n); }\n", n), foo(Dummy())
    {
        __assume(this); // without this, MSVC++ compiles two extra instructions checking if this==NULL and skipping the constructor call if it does
        new(this) bar(n, n);
    }
    ~bar()
    {
        printf("bar::~bar() {}\n");
    }
};

void main()
{
    printf("bar z(4);\n");
    bar z(4);
    printf("z.Get() == %d\n", z.Get());
}

输出:

bar z(4);
bar::bar(int n=4) : foo(Dummy()) { new(this) bar(n, n); }
foo::foo(Dummy) {}
bar::bar(int x=4, int y=4) : foo(x*y) {}
foo::foo(int n=16) : n(new int(n)) {}
z.Get() == 16
bar::~bar() {}
foo::~foo() { delete n; }

当然,如果基类具有常量*或引用成员(或者如果您不能编辑包含基类声明的文件),那么您运气不好。这样就不可能在其中编写一个虚拟构造函数 - 更不用说“new(this)”,然后你将两次初始化这些“常量”成员!这就是C ++ 0x委托构造函数真正可以派上用场的地方。

请告诉我这项技术还有什么其他问题仍然不安全或不便携。

(编辑:我也意识到可能在虚拟类中,虚函数表可能会被初始化两次。这将是无害的,但效率低下。我需要尝试一下,看看编译后的代码是什么样的。)

*如果您只在基类中拥有常量成员(并且没有引用),那么您并不是完全没有运气。您可以确保所有常量成员的所有类都有自己的伪构造函数,基类的伪构造函数可以依次调用它们。如果某些常量具有内置类型,例如 int ,那么你运气不好 - 那些将被不可避免地初始化(例如, const int 将被初始化为零)。

编辑:这是一个链接虚拟构造函数的示例,如果 int value 在类FooBar中变为 const int value ,它将被破坏:

#include <stdio.h>
#include <new>

struct Dummy {};

struct print
{
    print(const char *message)                    { fputs(message, stdout); }
    print(const char *format, int arg1)           { printf(format, arg1); }
    print(const char *format, int arg1, int arg2) { printf(format, arg1, arg2); }
};
struct print2 : public print
{
    print2(const char *message)                    : print(message) {}
    print2(const char *format, int arg1)           : print(format, arg1) {}
    print2(const char *format, int arg1, int arg2) : print(format, arg1, arg2) {}
};

class FooBar : public print
{
    int value;
public:
    FooBar() : print("FooBar::FooBar() : value(0x12345678) {}\n"), value(0x12345678) {}
    FooBar(Dummy) : print("FooBar::FooBar(Dummy) {}\n") {}
    int Get() const { return value; }
};

class foo : public print
{
    const FooBar j;
    int *n;
public:
    foo(Dummy) : print("foo::foo(Dummy) : j(Dummy) {}\n"), j(Dummy()) {}
    foo() : print("foo::foo() : n(new int), j() {}\n"), n(new int), j() {}
    foo(int n) : print("foo::foo(int n=%d) : n(new int(n)), j() {}\n", n), n(new int(n)), j() {}
    int Get() const { return *n; }
    int GetJ() const { return j.Get(); }
    ~foo()
    {
        printf("foo::~foo() { delete n; }\n");
        delete n;
    }
};

class bar : public print2, public foo
{
public:
    bar(int x, int y) : print2("bar::bar(int x=%d, int y=%d) : foo(x*y) {}\n", x, y), foo(x*y) {}
    bar(int n) : print2("bar::bar(int n=%d) : foo(Dummy()) { new(this) bar(n, n); }\n", n), foo(Dummy())
    {
        printf("GetJ() == 0x%X\n", GetJ());
        __assume(this); // without this, MSVC++ compiles two extra instructions checking if this==NULL and skipping the constructor call if it does
        new(this) bar(n, n);
    }
    ~bar()
    {
        printf("bar::~bar() {}\n");
    }
};

void main()
{
    printf("bar z(4);\n");
    bar z(4);
    printf("z.Get() == %d\n", z.Get());
    printf("z.GetJ() == 0x%X\n", z.GetJ());
}

输出:

bar z(4);
bar::bar(int n=4) : foo(Dummy()) { new(this) bar(n, n); }
foo::foo(Dummy) : j(Dummy) {}
FooBar::FooBar(Dummy) {}
GetJ() == 0xCCCCCCCC
bar::bar(int x=4, int y=4) : foo(x*y) {}
foo::foo(int n=16) : n(new int(n)), j() {}
FooBar::FooBar() : value(0x12345678) {}
z.Get() == 16
z.GetJ() == 0x12345678
bar::~bar() {}
foo::~foo() { delete n; }

(0xCCCCCCCC是在Debug版本中初始化未初始化的内存。)