views:

203

answers:

4
#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();

}
+5  A: 

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++.

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.

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.

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.

Johannes Schaub - litb
+1, curious. Would a compiler still be within the standard if they emitted a compilation error for this specific case (ignoring the potentially for reliably diagnosing this type of error in general)?
JaredPar
I am indeed a little surprised that neither MSVC nor GCC gives a warning on this type of erroneous coding since it is clearly wrong.
Steve Bush
JaredPard, thanks :) yes it would be valid for them to fail compiling programs that contains undefined behavior. the compiler can do everything it likes, including crashing the system that is running it :) (it happened to me when i had a recursive template instantiation - gone crazy with swapping!)
Johannes Schaub - litb
Steve, i don't know why they won't warn. indeed that's poor and i would like to have a compiler that knows when it happens and warns appropriately. maybe some next gen future compiler will do it :)
Johannes Schaub - litb
+2  A: 

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=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).

jpalecek
+1  A: 

You can't prevent people from coding poorly, really. It works just like it "should":

  1. Allocate memory for Foo (which is the value of the "this" pointer)
  2. Going to Foo::test by doing: Foo::test(this), in which,
  3. It gets the value by this->initialised, which is random junk, then it
  4. Calls Foo's default constructor (because of return Foo();), then
  5. Call Foo's copy constructor, to copy the right-handed Foo().

Just like it should. You can't prevent people from not knowing the right way to use 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.

But really, don't do this.

GMan
you are a purist :) C++ is billed as a multilevel language and we use application programmers AND system programmers on it. A lot of time is spent by people preventing classes from being used wrongly so I am surprised compilers dont warn about this. Your MAGIC NUMBER is my solution :))
Steve Bush
=| I still strongly against the magic number idea, oh what have I done! :OMaybe if you detail more of what you are trying to prevent, there is a more proper way to handle it.
GMan
:) I defined a class called "var" which allows non-C++ programmers to use C++ for programming like the following.var aa=1;var bb=2;print(aa+bb);plus a whole load of special functions related to our business.
Steve Bush
You should at least overwrite the magic number with something else in the destructor, to prevent a new object allocated in the same place reading the magic number from the previous instance.
jpalecek
then by mistake some of them REDECLARE a variable when they want to self modify it. eg var aaa;for (blah) {var aaa=aaa.dosomethingtome();}
Steve Bush
good point! I will :)
Steve Bush
I tried blocking copy assignment but the compilers optimise that away.
Steve Bush
=/ I still think you should solve the problem rather than redirect it. That is, tell them when they mess up and redefine it.
GMan
there is no way to tell them since the error is totally silent at compile time AND run time. c++ experts are used to this type of stupidity - but we are using it for application level programming where the programmers dont comprehend "undefined behaviour"
Steve Bush
Hm, well then best of luck to you :) You can make this debug only and in release not do these checks, for speed.
GMan
all done and sorted!
Steve Bush
A: 

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:

  • 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.

Michael Burr
Thanks. Dogtag idea seems useful in extremely hostile environments. Trashing memory eeek.
Steve Bush