在对象初始化之前在对象上运行的方法?

时间:2009-04-06 21:38:30

标签: c++ initialization

#include <iostream>
using namespace std;

class Foo
{

public:

 Foo(): initialised(0)
 {
  cout << "Foo() gets called AFTER test() ?!" << endl;
 };

 Foo test()
 {
  cout << "initialised= " << initialised << " ?! - ";
  cout << "but I expect it to be 0 from the 'initialised(0)' initialiser on Foo()" << endl;
  cout << "this method test() is clearly working on an uninitialised object ?!" << endl;
  return Foo();
 }

 ~Foo()
 {};

private:

 int initialised;

};


int main()
{

 //SURE this is bad coding but it compiles and runs
 //I want my class to DETECT and THROW an error to prevent this type of coding
 //in other words how to catch it at run time and throw "not initialised" or something

 Foo foo=foo.test();

}

4 个答案:

答案 0 :(得分:5)

是的,它是在一个尚未构造的对象上调用该函数,这是未定义的行为。你无法检测到它的可靠性。我认为你也不应该试图发现它。与例如在已删除的对象上调用函数相比,这可能是偶然发生的。试图捕捉所有可能的错误几乎是不可能的。声明的名称在其初始化程序中已经可见,用于其他有用的目的。考虑一下:

Type *t = (Type*)malloc(sizeof(*t)); 

这是C编程中常见的习惯用法,它仍然适用于C ++。

就个人而言,我喜欢Herb Sutter的this story关于空引用(同样无效)。要点是,不要试图保护语言明确禁止的情况,特别是在一般情况下不可能可靠地诊断。随着时间的推移,你会得到一个虚假的安全,这变得非常危险。相反,培养您对语言的理解和设计界面(避免原始指针......),以减少出错的可能性。

在C ++中,同样在C语言中,许多情况并未明确禁止,而是未定义。部分是因为有些事情很难有效诊断部分因为未定义的行为让实现为它设计了替代行为而不是完全忽略它 - 现有编译器经常使用它。

在上面的例子中,任何实现都可以自由地抛出异常。还有其他情况同样是未定义的行为,这对于实现来说更难以有效诊断:在构造之前访问不同转换单元中的对象就是这样一个例子 - 这被称为静态初始化顺序fiasco

答案 1 :(得分:2)

构造函数是你想要的方法(在初始化之前没有运行,而是在初始化时运行,但是应该没问题)。它在你的情况下不起作用的原因是你在这里有未定义的行为。

特别是,您使用尚未存在的foo对象来初始化自身(例如,foo中的foo.Test()尚不存在)。您可以通过显式创建对象来解决它:

Foo foo=Foo().test()

您无法在程序中检查它,但valgrind可能会发现此类错误(与任何其他未初始化的内存访问一样)。

答案 2 :(得分:1)

真的,你不能阻止人们编码不好。它就像“应该”一样工作:

  1. 为Foo分配内存(这是“this”指针的值)
  2. 去做Foo ::测试:Foo :: test(this),其中,
  3. 它通过this-&gt;初始化获得值,这是随机垃圾,然后是
  4. 调用Foo的默认构造函数(因为返回Foo();),然后是
  5. 调用Foo的复制构造函数,复制右手的Foo()。
  6. 就像它应该的那样。你不能阻止人们不知道使用C ++的正确方法。

    你能做的最好的事情就是一个神奇的数字:

    class A
    {
    public:
        A(void) :
        _magicFlag(1337)
        {
        }
    
        void some_method(void)
        {
            assert (_magicFlag == 1337); /* make sure the constructor has been called */
        }
    
    private:
        unsigned _magicFlag;
    }
    

    这“有效”,因为在值已经是1337的情况下分配了_magicFlag的机会很低。

    但实际上,不要这样做。

答案 3 :(得分:0)

你得到了很多回复,基本上说,“你不应该期望编译器能帮你解决这个问题”。但是,我同意你的意见,编译器应该通过某种诊断来帮助解决这个问题。不幸的是(正如其他答案所指出的那样),语言规范在这里没有帮助 - 一旦你到达声明的初始化部分,新声明的标识符就在范围内。

前段时间,DDJ有一篇关于a simple debugging class called "DogTag"的文章可以作为调试助手来帮助:

  • 删除后使用对象
  • 用垃圾覆盖对象的内存
  • 在初始化之前使用对象

我没有太多使用它 - 但是它确实在一个嵌入式项目中出现,该项目遇到了一些内存覆盖错误。

这基本上是the "MagicFlag" technique that GMan described的详细说明。