#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 个解决方案
#1
You can't prevent people from coding poorly, really. It works just like it "should":
你不能阻止人们编码不好,真的。它就像“应该”一样工作:
- Allocate memory for Foo (which is the value of the "this" pointer)
- Going to Foo::test by doing: Foo::test(this), in which,
- It gets the value by this->initialised, which is random junk, then it
- Calls Foo's default constructor (because of return Foo();), then
- Call Foo's copy constructor, to copy the right-handed Foo().
为Foo分配内存(这是“this”指针的值)
去做Foo ::测试:Foo :: test(this),其中,
它通过this-> initialised得到了值,这是随机垃圾,然后是它
调用Foo的默认构造函数(因为返回Foo();),然后
调用Foo的复制构造函数,复制右手的Foo()。
Just like it should. You can't prevent people from not knowing the right way to use C++.
就像它应该的那样。你不能阻止人们不知道使用C ++的正确方法。
The best you could do is have a magic number:
你能做的最好的就是一个神奇的数字:
class A
{
public:
A(void) :
_magicFlag(1337)
{
}
void some_method(void)
{
assert (_magicFlag == 1337); /* make sure the constructor has been called */
}
private:
unsigned _magicFlag;
}
This "works" because the chances _magicFlag gets allocated where the value is already 1337 is low.
这“有效”,因为_magicFlag在值已经为1337的情况下被分配的可能性很低。
But really, don't do this.
但实际上,不要这样做。
#2
Yes, it is calling the function on a yet not constructed object, which is undefined behavior. You can't detect it reliable. I would argue you also should not try to detect it. It's nothing which would happen likely by accident, compared to for example calling a function on an already deleted object. Trying to catch every and all possible mistakes is just about impossible. The name declared is visible already in its initializer, for other useful purposes. Consider this:
是的,它在一个尚未构造的对象上调用该函数,这是未定义的行为。你无法检测它是否可靠。我认为你也不应该试图发现它。与例如在已删除的对象上调用函数相比,这可能是偶然发生的。试图捕捉所有可能的错误几乎是不可能的。声明的名称在初始化程序中已经可见,用于其他有用的目的。考虑一下:
Type *t = (Type*)malloc(sizeof(*t));
Which is a common idiom in C programming, and which still works in C++.
这是C编程中常见的习惯用法,它仍然适用于C ++。
Personally, i like this story by Herb Sutter about null references (which are likewise invalid). The gist is, don't try to protect from cases that the language clearly forbids and in particular are in their general case impossible to diagnose reliably. You will get a false security over time, which becomes quite dangerous. Instead, train your understanding of the language and design interfaces in a way (avoid raw pointers, ...) that reduces the chance of doing mistakes.
就个人而言,我喜欢Herb Sutter关于空引用(同样无效)的故事。要点是,不要试图保护语言明确禁止的情况,特别是在一般情况下不可能可靠地诊断。随着时间的推移,你会得到一个虚假的安全,这变得非常危险。相反,培养您对语言的理解和设计界面(避免原始指针......),以减少出错的可能性。
In C++ and likewise in C, many cases are not explicitly forbidden, but rather are left undefined. Partially because some things are rather difficult to diagnose efficiently and partially because undefined behavior lets the implementation design alternative behavior for it instead of completely ignoring it - which is used often by existing compilers.
在C ++中,同样在C语言中,许多情况并未明确禁止,而是未定义。部分原因是有些事情很难有效地部分诊断,因为未定义的行为允许实现为它设计替代行为而不是完全忽略它 - 现有编译器经常使用它。
In the above case for example, any implementation is free to throw an exception. There are other situations that are likewise undefined behavior which are much harder to diagnose efficiently for the implementation: Having an object in a different translation unit accessed before it was constructed is such an example - which is known as the static initialization order fiasco.
例如,在上述情况下,任何实现都可以*地抛出异常。还有其他情况同样是未定义的行为,对于实现来说,更难以有效地诊断:在构造之前访问不同转换单元中的对象就是这样一个例子 - 这被称为静态初始化顺序fiasco。
#3
The constructor is the method you want (not running before initialization but rather on initialization, but that should be OK). The reason it doesn't work in your case is that you have undefined behavior here.
构造函数是您想要的方法(在初始化之前不运行,而是在初始化时运行,但这应该没问题)。它在你的情况下不起作用的原因是你在这里有未定义的行为。
Particularly, you use the not-yet-existent foo object to initialize itself (eg. the foo
in foo.Test()
doesn't exist yet). You can solve it by creating an object explicitly:
特别是,您使用尚未存在的foo对象来初始化自身(例如,foo.Test()中的foo尚不存在)。您可以通过显式创建对象来解决它:
Foo foo=Foo().test()
You cannot check for it in the program, but maybe valgrind could find this type of bug (as any other uninitialized memory access).
您无法在程序中检查它,但valgrind可能会发现此类错误(与任何其他未初始化的内存访问一样)。
#4
You're getting quite a few responses that basically say, "you shouldn't expect the compiler to help you with this". However, I'd agree with you that the compiler should help with this problem by with some sort of diagnostic. Unfortunately (as the other answers point out), the language spec doesn't help here - once you get to the initializer part of the declaration, the newly declared identifier is in scope.
你得到了很多回复,基本上说,“你不应该期望编译器帮助你解决这个问题”。但是,我同意你的意见,编译器应该通过某种诊断来帮助解决这个问题。不幸的是(正如其他答案所指出的那样),语言规范在这里没有帮助 - 一旦你到达声明的初始化部分,新声明的标识符就在范围内。
A while back, DDJ had an article about a simple debugging class called "DogTag" that could be used as a debugging aid to help with:
前段时间,DDJ有一篇关于一个名为“DogTag”的简单调试类的文章,它可以用作调试辅助工具来帮助:
- using an object after deletion
- overwriting an object's memory with garbage
- using an object before initializing it
删除后使用对象
用垃圾覆盖对象的内存
在初始化之前使用对象
I haven't used it much - but it did come in handly on an embedded project that was running into some memory overwrite bugs.
我没有太多使用它 - 但它确实出现在一个嵌入式项目中,该项目遇到了一些内存覆盖错误。
It's basically an elaboration of the "MagicFlag" technique that GMan described.
它基本上是对GMan所描述的“MagicFlag”技术的阐述。
#1
You can't prevent people from coding poorly, really. It works just like it "should":
你不能阻止人们编码不好,真的。它就像“应该”一样工作:
- Allocate memory for Foo (which is the value of the "this" pointer)
- Going to Foo::test by doing: Foo::test(this), in which,
- It gets the value by this->initialised, which is random junk, then it
- Calls Foo's default constructor (because of return Foo();), then
- Call Foo's copy constructor, to copy the right-handed Foo().
为Foo分配内存(这是“this”指针的值)
去做Foo ::测试:Foo :: test(this),其中,
它通过this-> initialised得到了值,这是随机垃圾,然后是它
调用Foo的默认构造函数(因为返回Foo();),然后
调用Foo的复制构造函数,复制右手的Foo()。
Just like it should. You can't prevent people from not knowing the right way to use C++.
就像它应该的那样。你不能阻止人们不知道使用C ++的正确方法。
The best you could do is have a magic number:
你能做的最好的就是一个神奇的数字:
class A
{
public:
A(void) :
_magicFlag(1337)
{
}
void some_method(void)
{
assert (_magicFlag == 1337); /* make sure the constructor has been called */
}
private:
unsigned _magicFlag;
}
This "works" because the chances _magicFlag gets allocated where the value is already 1337 is low.
这“有效”,因为_magicFlag在值已经为1337的情况下被分配的可能性很低。
But really, don't do this.
但实际上,不要这样做。
#2
Yes, it is calling the function on a yet not constructed object, which is undefined behavior. You can't detect it reliable. I would argue you also should not try to detect it. It's nothing which would happen likely by accident, compared to for example calling a function on an already deleted object. Trying to catch every and all possible mistakes is just about impossible. The name declared is visible already in its initializer, for other useful purposes. Consider this:
是的,它在一个尚未构造的对象上调用该函数,这是未定义的行为。你无法检测它是否可靠。我认为你也不应该试图发现它。与例如在已删除的对象上调用函数相比,这可能是偶然发生的。试图捕捉所有可能的错误几乎是不可能的。声明的名称在初始化程序中已经可见,用于其他有用的目的。考虑一下:
Type *t = (Type*)malloc(sizeof(*t));
Which is a common idiom in C programming, and which still works in C++.
这是C编程中常见的习惯用法,它仍然适用于C ++。
Personally, i like this story by Herb Sutter about null references (which are likewise invalid). The gist is, don't try to protect from cases that the language clearly forbids and in particular are in their general case impossible to diagnose reliably. You will get a false security over time, which becomes quite dangerous. Instead, train your understanding of the language and design interfaces in a way (avoid raw pointers, ...) that reduces the chance of doing mistakes.
就个人而言,我喜欢Herb Sutter关于空引用(同样无效)的故事。要点是,不要试图保护语言明确禁止的情况,特别是在一般情况下不可能可靠地诊断。随着时间的推移,你会得到一个虚假的安全,这变得非常危险。相反,培养您对语言的理解和设计界面(避免原始指针......),以减少出错的可能性。
In C++ and likewise in C, many cases are not explicitly forbidden, but rather are left undefined. Partially because some things are rather difficult to diagnose efficiently and partially because undefined behavior lets the implementation design alternative behavior for it instead of completely ignoring it - which is used often by existing compilers.
在C ++中,同样在C语言中,许多情况并未明确禁止,而是未定义。部分原因是有些事情很难有效地部分诊断,因为未定义的行为允许实现为它设计替代行为而不是完全忽略它 - 现有编译器经常使用它。
In the above case for example, any implementation is free to throw an exception. There are other situations that are likewise undefined behavior which are much harder to diagnose efficiently for the implementation: Having an object in a different translation unit accessed before it was constructed is such an example - which is known as the static initialization order fiasco.
例如,在上述情况下,任何实现都可以*地抛出异常。还有其他情况同样是未定义的行为,对于实现来说,更难以有效地诊断:在构造之前访问不同转换单元中的对象就是这样一个例子 - 这被称为静态初始化顺序fiasco。
#3
The constructor is the method you want (not running before initialization but rather on initialization, but that should be OK). The reason it doesn't work in your case is that you have undefined behavior here.
构造函数是您想要的方法(在初始化之前不运行,而是在初始化时运行,但这应该没问题)。它在你的情况下不起作用的原因是你在这里有未定义的行为。
Particularly, you use the not-yet-existent foo object to initialize itself (eg. the foo
in foo.Test()
doesn't exist yet). You can solve it by creating an object explicitly:
特别是,您使用尚未存在的foo对象来初始化自身(例如,foo.Test()中的foo尚不存在)。您可以通过显式创建对象来解决它:
Foo foo=Foo().test()
You cannot check for it in the program, but maybe valgrind could find this type of bug (as any other uninitialized memory access).
您无法在程序中检查它,但valgrind可能会发现此类错误(与任何其他未初始化的内存访问一样)。
#4
You're getting quite a few responses that basically say, "you shouldn't expect the compiler to help you with this". However, I'd agree with you that the compiler should help with this problem by with some sort of diagnostic. Unfortunately (as the other answers point out), the language spec doesn't help here - once you get to the initializer part of the declaration, the newly declared identifier is in scope.
你得到了很多回复,基本上说,“你不应该期望编译器帮助你解决这个问题”。但是,我同意你的意见,编译器应该通过某种诊断来帮助解决这个问题。不幸的是(正如其他答案所指出的那样),语言规范在这里没有帮助 - 一旦你到达声明的初始化部分,新声明的标识符就在范围内。
A while back, DDJ had an article about a simple debugging class called "DogTag" that could be used as a debugging aid to help with:
前段时间,DDJ有一篇关于一个名为“DogTag”的简单调试类的文章,它可以用作调试辅助工具来帮助:
- using an object after deletion
- overwriting an object's memory with garbage
- using an object before initializing it
删除后使用对象
用垃圾覆盖对象的内存
在初始化之前使用对象
I haven't used it much - but it did come in handly on an embedded project that was running into some memory overwrite bugs.
我没有太多使用它 - 但它确实出现在一个嵌入式项目中,该项目遇到了一些内存覆盖错误。
It's basically an elaboration of the "MagicFlag" technique that GMan described.
它基本上是对GMan所描述的“MagicFlag”技术的阐述。