When is it safe to call this-> in constructor and

2019-01-13 17:56发布

I've not been able to find a conclusive answer to this so far. When is it safe to call this-> from within an object. And in particular from inside the constructor and destructor.

And also, when using public inheritance. Is it safe to use up and downcasting on the result of the this call?

So for example:

class foo
{
   foo():
   a(),
   b(this->a)//case 1
   {
       this-> a = 5; //case 2
   }

   int a;
   int b;
};

class bar: public baz
{
   bar():
   baz(this)//case 3 - assuming baz has a valid constructor
   {


   }

}

And finally the most unlikely one

foo()
   {
      if(static_cast<bar*>(this));//case 4
   }

Which of the above cases are legal?

Note: I'm aware a lot of the practices above are inadvisable.

3条回答
爷、活的狠高调
2楼-- · 2019-01-13 18:31

There is a good entry on it at the C++ super-faq:

https://isocpp.org/wiki/faq/ctors#using-this-in-ctors

Some people feel you should not use the this pointer in a constructor because the object is not fully formed yet. However you can use this in the constructor (in the {body} and even in the initialization list) if you are careful.

Here is something that always works: the {body} of a constructor (or a function called from the constructor) can reliably access the data members declared in a base class and/or the data members declared in the constructor’s own class. This is because all those data members are guaranteed to have been fully constructed by the time the constructor’s {body} starts executing.

Here is something that never works: the {body} of a constructor (or a function called from the constructor) cannot get down to a derived class by calling a virtual member function that is overridden in the derived class. If your goal was to get to the overridden function in the derived class, you won’t get what you want. Note that you won’t get to the override in the derived class independent of how you call the virtual member function: explicitly using the this pointer (e.g., this->method()), implicitly using the this pointer (e.g., method()), or even calling some other function that calls the virtual member function on your this object. The bottom line is this: even if the caller is constructing an object of a derived class, during the constructor of the base class, your object is not yet of that derived class. You have been warned.

Here is something that sometimes works: if you pass any of the data members in this object to another data member’s initializer, you must make sure that the other data member has already been initialized. The good news is that you can determine whether the other data member has (or has not) been initialized using some straightforward language rules that are independent of the particular compiler you’re using. The bad news is that you have to know those language rules (e.g., base class sub-objects are initialized first (look up the order if you have multiple and/or virtual inheritance!), then data members defined in the class are initialized in the order in which they appear in the class declaration). If you don’t know these rules, then don’t pass any data member from the this object (regardless of whether or not you explicitly use the this keyword) to any other data member’s initializer! And if you do know the rules, please be careful.

查看更多
对你真心纯属浪费
3楼-- · 2019-01-13 18:33

The this pointer is accessible in every non-static member function ...

§9.3.2/1

In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*. If the member function is declared const, the type of this is const X*, if the member function is declared volatile, the type of this is volatile X*, and if the member function is declared const volatile, the type of this is const volatile X*.

... where constructors and destructors are member functions ...

§12/1

The default constructor (12.1), copy constructor and copy assignment operator (12.8), move constructor and move assignment operator (12.8), and destructor (12.4) are special member functions.

... which aren't static.

§12.1/4

A constructor shall not be virtual (10.3) or static (9.4).

§12.4/2

A destructor shall not be static.

Thus, this is available in constructors and destructors. But there are limitations (especially with respect to the use of this inside the initializer list).

(Note: Inside the constructor / destructor body, the initialization of all subobjects and members is completed and they are accessible; see further down below).

1. Only access object being constructed (or their subobjects) through this.

§12.1/14

During the construction of a const object, if the value of the object or any of its subobjects is accessed through a glvalue that is not obtained, directly or indirectly, from the constructor’s this pointer, the value of the object or subobject thus obtained is unspecified.

2. Do not call virtual functions that are overriden in a derived class in the base constructor

§12.7/4

Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more derived class. If the virtual function call uses an explicit class member access (5.2.5) and the object expression refers to the complete object of x or one of that object’s base class subobjects but not x or one of its base class subobjects, the behavior is undefined.

3. Do not apply dynamic_cast to cast this into any type other than the type under construction or any base type thereof.

§12.7/6

dynamic_casts (5.2.7) can be used during construction or destruction (12.6.2). When a dynamic_cast is used in a constructor (including the mem-initializer or brace-or-equal-initializer for a non-static data member) or in a destructor, or used in a function called (directly or indirectly) from a constructor or destructor, if the operand of the dynamic_cast refers to the object under construction or destruction, this object is considered to be a most derived object that has the type of the constructor or destructor’s class. If the operand of the dynamic_cast refers to the object under construction or destruction and the static type of the operand is not a pointer to or object of the constructor or destructor’s own class or one of its bases, the dynamic_cast results in undefined behavior.

4. Conversion of this into base type pointer is only allowed through paths that consist of constructed base types.

§12.7/3

To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior. 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.

Accessing sub-objects and members in the initializer list and the constructor body

In principle you can access constructed / initialized objects from the initializer list, if their initialization takes place before accessing them. The order of initialization is

§12.6.2/10

In a non-delegating constructor, initialization proceeds in the following order:

  • First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.

  • Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).

  • Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

  • Finally, the compound-statement of the constructor body is executed.

查看更多
Explosion°爆炸
4楼-- · 2019-01-13 18:50

Within any non-static member function, this points to the object that the function was called on. It's safe to use as long as that's a valid object.

Within the body of a constructor or destructor, there is a valid object of the class currently being constructed. However, if this is the base sub-object of some derived class, then only the base sub-object is valid at that time; so it's generally not safe to down-cast and try to access members of the derived class. For the same reason, you need to be careful calling virtual functions here, since they are dispatched according to the class being created or destroyed, not the final overrider.

Within the initialiser list of a constructor, you'll need to be careful only to access members that have been initialised; that is, members declared before the one currently being initialised.

Up-casting to a base class is always safe, since base sub-objects are always initialised first.

For the specific examples you just added to the question:

  • case 1 is fine (if fragile), since a has been initialised at that point. Initialising a with the value of b would be undefined, since b is initialised after a.
  • case 2 is fine: all members have been initialised at that point.
  • case 3 won't compile, since there's no suitable foo constructor. If there were, then it would depend on what that constructor did with it - whether or not it tried to access members before they were initialised.
  • case 4 would be well-formed if you added the missing ), but dangerous if you tried to use the pointer to access the object. this does not yet point to a valid bar object (only the foo part has been initialised) so accessing members of bar could give undefined behaviour. Simply checking whether the pointer is non-null is fine, and will always give true (whether or not you apply a pointless cast).
查看更多
登录 后发表回答