代码之家  ›  专栏  ›  技术社区  ›  Brandon Bodnar

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

c++
  •  18
  • Brandon Bodnar  · 技术社区  · 16 年前

    这是最近在一个我是助教的班级里发现的。我们教学生如何在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 回复  |  直到 10 年前
        1
  •  16
  •   jjnguy Julien Chastang    16 年前

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

    在那之前, new(this) 作品 一些 案例,但不是全部。尤其是,在构造函数中,您的基类已经完全构造好了。重建VIA 新的(这个) 在不调用基析构函数的情况下重新调用基构造函数,因此,如果基类不期望发生这种黑客行为,则会出现问题,而它们可能没有。

    一个清晰的例子:

    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
        }
    }
    

    或者如他们所说的“欢闹随之而来”

        2
  •  11
  •   Mike Seymour    16 年前

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

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

        3
  •  5
  •   Nate    10 年前

    下面是关于C++的常见问题 question ,一个类的一个构造函数是否可以调用同一类的另一个构造函数来初始化该对象?艾斯:

    顺便说一句,不要试图通过新位置来实现这一点。有些人认为他们可以说 new(this) Foo(x, int(x)+7) 在…体内 Foo::Foo(char) . 但那是坏的,坏的,坏的。请不要写信告诉我 似乎 处理特定编译器的特定版本;这很糟糕。构造器在幕后做了很多小的神奇的事情,但是那些不好的技术是在那些部分构造的部分上进行的。只说不。

        4
  •  3
  •   pestilence669    16 年前

    除非您试图调用父级的构造函数,否则我建议您创建一个私有的初始化方法。没有理由不能跨构造函数调用共享初始值设定项。

        5
  •  1
  •   Björn Pollex    16 年前

    如果您有这样的构造函数,则此方法不起作用:

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

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

        6
  •  1
  •   Jerry Coffin    16 年前

    正如所写的代码一样,它应该可以工作——尽管我不能确切地想象为什么要这样写代码。特别是,它取决于这样一个事实:所有的指针都只用于引用一个in t。既然如此,为什么不直接输入一个in t呢? 在里面 对象,而不是使用指针并动态分配int?简言之,他们所拥有的是长期的和低效的,但与以下方面没有显著区别:

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

    不幸的是,当你试图从指针中获得一些真正的使用时(例如,在不同的对象中分配不同数量的内存),它们使用的“技巧”与“放置新的”停止工作——这完全取决于每个对象分配的内存量完全相同。由于每个分配中的分配都是完全相同的,为什么要将该分配放在堆中而不是使其成为对象本身的一部分?

    说实话,在那儿 在有意义的情况下。然而,我能马上想到的唯一一个问题是,分配量很大 您运行的环境中堆空间比堆栈空间大得多。

    代码可以工作,但它只在相当狭窄的特定情况下才有用。我并不认为这是我作为一个如何做事情的例子所推荐的。

        7
  •  0
  •   Deadcode    13 年前

    在我看来,即使在派生类的构造函数中也可以安全地使用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)”来初始化这些“constant”成员两次!这是真正的事情,C++0X委托构造函数,真的可以派上用场。

    请告诉我这项技术是否还有其他不安全或不可移植的地方。

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

    *如果您在基类中只有常量成员(并且没有引用),那么您就不会完全走运。您只需确保所有常量成员的所有类都有自己的伪构造函数,基类的伪构造函数可以依次调用这些伪构造函数。如果一些常量有内置类型,比如 int 尽管这些将不可避免地被初始化(例如 常数int 将初始化为零)。

    编辑:这里有一个链接虚拟构造函数的例子,如果 int值 成为 常量int值 内部foobar类:

    #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; }
    

    (0xCCCCCC是未初始化内存在调试版本中初始化的内容。)