Can I use C++ class members initialized in the initializer list, later in the list?
Asked Answered
I

2

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?"

Indented answered 19/2, 2013 at 20:12 Comment(0)
H
9

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.

Holler answered 19/2, 2013 at 20:16 Comment(6)
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).Santamaria
It is legal to pass, but semantically wrong, as you'd be passing a pointer to something that hasn't been constructed yet.Sizeable
@AlexChamberlain: Nothing wrong with it necessarily, but probably needs a double check.Greig
If you're the consumer of the pointer, how do you tell though?Sizeable
@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.Holler
@Alex One could say it's semantically not wrong because a pointer stores the address, not the object.Fixative
S
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;
}
Sizeable answered 19/2, 2013 at 20:15 Comment(4)
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.Nutrient
@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.Santamaria
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.Sizeable
@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.Nutrient

© 2022 - 2024 — McMap. All rights reserved.