25

Can I initialize a pointer to a data member before initializing the member? In other words, is this valid C++?

#include <string>

class Klass {
public:
    Klass()
        : ptr_str{&str}
        , str{}
    {}
private:
    std::string *ptr_str;
    std::string str;
};

this question is similar to mine, but the order is correct there, and the answer says

I'd advise against coding like this in case someone changes the order of the members in your class.

Which seems to mean reversing the order would be illegal but I couldn't be sure.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93

2 Answers2

28

Does a member have to be initialized to take its address?

No.

Can I initialize a pointer to a data member before initializing the member? In other words, is this valid C++?

Yes. Yes.

There is no restriction that operand of unary & need to be initialised. There is an example in the standard in specification of unary & operator:

int a;
int* p1 = &a;

Here, the value of a is indeterminate and it is OK to point to it.

What that example doesn't demonstrate is pointing to an object before its lifetime has begun, which is what happens in your example. Using a pointer to an object before and after its lifetime is explicitly allowed if the storage is occupied. Standard draft says:

[basic.life] Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways ...

The rule goes on to list how the usage is restricted. You can get by with common sense. In short, you can treat it as you could treat a void*, except violating these restrictions is UB rather than ill-formed. Similar rule exists for references.

There are also restrictions on computing the address of non-static members specifically. Standard draft says:

[class.cdtor] ... To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.

In the constructor of Klass, the construction of Klass has started and destruction hasn't completed, so the above rule is satisfied.


P.S. Your class is copyable, but the copy will have a pointer to the member of another instance. Consider whether that makes sense for your class. If not, you will need to implement custom copy and move constructors and assignment operators. A self-reference like this is a rare case where you may need custom definitions for those, but not a custom destructor, so it is an exception to the rule of five (or three).

P.P.S If your intention is to point to one of the members, and no object other than a member, then you might want to use a pointer to member instead of pointer to object.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 7
    It would be nice to quote relevant parts of the Standard. These might be relevant: http://eel.is/c++draft/basic.life#6.sentence-1 and http://eel.is/c++draft/class.cdtor#3.sentence-2. – Daniel Langr May 10 '19 at 13:58
  • 1
    @DanielLangr Thanks for looking those up. – eerorika May 10 '19 at 14:05
  • 1
    There's a subtle difference between your example and the OP's example. In your example, the lifetime of the variable `a` has begun, but it was not initialized. In the OP's example, the address of a member is taken before its lifetime. Given that your quotations of the standard already adequately address the OP's question, I think the example should be deleted. – Brian Bi May 10 '19 at 14:57
  • 1
    @Brian OP's specifically asks about initialization. The quoted rule doesn't explicitly address initialization (no rule addresses it; there simply isn't a rule related to initialization to my knowledge). Sure, lack of lifetime implies lack of initialization, but I prefer to be explicit. In my opinion, the example demonstrating lack of initialization is useful even if it is technically made redundant by the lifetime rule. – eerorika May 10 '19 at 15:10
  • [this](https://stackoverflow.com/questions/61780925/why-cant-i-use-uniform-initialization-when-i-want-to-override-a-value/61781021#comment109280173_61781021) comment says `int a;` *is* initialized. Default-initialized (with an indeterminate value). – Aykhan Hagverdili May 13 '20 at 20:37
  • @Ayxan Initialised with indeterminate value and uninitialised are two ways to say the same thing. "Default initialisation" is a formal name for the syntax. "Uninitialised" is a description of the behaviour that default initialisation has in this case. – eerorika May 13 '20 at 21:03
-3

Funny question.

It is legitimate and will "work", though barely. There is a little "but" related to types which makes the whole thing a bit awkward with a bad taste (but not illegitimate), and which might make it illegal some border cases involving inheritance.

You can, of course, take the address of any object whether it's initialized or not, as long as it exists in the scope and has a name which you can prepend operator& to. Dereferencing the pointer is a different thing, but that wasn't the question.

Now, the subtle problem is that the standard defines the result of operator& for non-static struct members as "“pointer to member of class C of type T” and is a prvalue designating C::m".

Which basically means that ptr_str{&str} will take the address of str, but the type is not pointer-to, but pointer-to-member-of. It is then implicitly and silently cast to pointer-to.

In other words, although you do not need to explicitly write &this->str, that's nevertheless what its type is -- it's what it is and what it means [1].

Is this valid, and is it safe to use it within the initializer list? Well yes, just... barely. It's safe to use it as long as it's not being used to access uninitialized members or virtual functions, directly or indirectly. Which, as it happens, is the case here (it might not be the case in a different, arguably contrived case).


[1] Funnily, paragraph 4 starts with a clause that says that no member pointer is formed when you put stuff in parentheses. That's remarkable because most people would probably do that just to be 100% sure they got operator precedence right. But if I read correctly, then &this->foo and &(this->foo) are not in any way the same!
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
Damon
  • 67,688
  • 20
  • 135
  • 185
  • 2
    No, `&` with an unqualified name *never* creates a pointer-to-member. If you wanted a pointer-to-member, you would have to write `&Klass::str`. Similarly, `&this->foo` and `&(this->foo)` are the same, both create ordinary pointers because these are member-access expressions, not qualified names. – Ben Voigt May 10 '19 at 17:50
  • 2
    The Standard rule you link to starts out with "If the operand is a *qualified-id*" Which is why it does not apply here. – Ben Voigt May 10 '19 at 17:52
  • 1
    Moreover, there are no implicit conversions from a pointer-to-member type to a pointer type (what object would it bind to?). – Sneftel May 10 '19 at 17:54