10

I am rewriting some code to eliminate global variables and made a class constructor/destructor handle cleanup of some third party library resources, but I am concerned about some code which initializes one member from another member in the class initializer list.

class MyPodofoDocument {
public:
    // generates pdf to stream
    MyPodofoDocument(std::stringstream *pStringStream)
        : device(pStringStream), document(&device)
    {
    }
private:
    PoDoFo::PdfOutputDevice device;
    PoDoFo::PdfStreamedDocument document;
    PoDoFo::PdfPainter painter;
};

The code which uses this class doesn't need to see all the details that go into using the library, but the way I hide them makes it dependent on using members to initialize other members, before it hits the constructor's actual code block, where it has a valid this pointer.

It works in a unit test skeleton, so my question is basically, "Is this okay, portable and safe?"

Qamar Suleiman
  • 1,228
  • 2
  • 18
  • 31
Kenny Ostrom
  • 5,639
  • 2
  • 21
  • 30

2 Answers2

8

The members are initialized in the order they are declared, top to bottom

PoDoFo::PdfOutputDevice device;
PoDoFo::PdfStreamedDocument document;
PoDoFo::PdfPainter painter;

so it is safe to use device to initialize document.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • Furthermore, it is legal to obtain the address or bind a reference to a yet to be constructed member (i.e. you can pass a reference to a member declared later if the receiver does not *use* the object, but only stores the reference/pointer). – David Rodríguez - dribeas Feb 19 '13 at 20:48
  • It is legal to pass, but semantically wrong, as you'd be passing a pointer to something that hasn't been constructed yet. – Alex Chamberlain Feb 19 '13 at 20:53
  • @AlexChamberlain: Nothing wrong with it necessarily, but probably needs a double check. – GManNickG Feb 19 '13 at 21:12
  • If you're the consumer of the pointer, how do you tell though? – Alex Chamberlain Feb 19 '13 at 21:12
  • 1
    @Alex - You just have to be careful! The standard library's stream classes pass a pointer to its `streambuf` member to the stream's base class constructor, at a time when the `streambuf` is not yet constructed. Of course the base class knows it should just store the pointer for later, and use it only after construction is complete. – Bo Persson Feb 19 '13 at 21:18
  • @Alex One could say it's semantically not wrong because a pointer stores the address, not the object. – Dean Mar 26 '21 at 01:34
4

Kind of. The rules is that the member variables are initialised in the order they are declared in the class declaration.

In your case, it is fine since device is declared before document.

However, in the following case, we have undefined behaviour, despite the order of the initialiser list.

class A {
public:
  A(int i) : b(i), a(b) { }
private:
  int a;
  int b;
}
Alex Chamberlain
  • 4,147
  • 2
  • 22
  • 49
  • 2
    Even though it is safe *as long as the dependencies between the variables are the same as the declaration order*, IMHO it is not very good practice. Such code should be avoided whenever possible if only because it is all too easy to mistakenly modify the declaration order while refactoring your class, thus triggering UB as Alex's example shows it. In other words, this is correct but very fragile. – syam Feb 19 '13 at 20:24
  • 1
    @syam: I am not too sure... I used to think the same, but it is very easy to get the compiler to warn you if the initializers are out of order (the compiler will call your attention to the potential UB) and in some cases it might help to reference a different member of the same type. – David Rodríguez - dribeas Feb 19 '13 at 20:49
  • It's not just useful for things of the same type of course; anything where there is a real dependence between variables, but where you need to store the variables as well. – Alex Chamberlain Feb 19 '13 at 20:51
  • @DavidRodríguez-dribeas You are right I just checked with g++ 4.7 it has `-Wreorder` (included in `-Wall`) which catches this very situation. I didn't know about it, the older compilers I used to work with didn't have this kind of warning, and old habits die hard. – syam Feb 19 '13 at 21:04