4

Suppose I have a class Baz that inherits from classes Foo and Bar, in that order. The constructor for class Bar takes a pointer to a Foo object. What I would like to do is to pass this as the Foo object to the Bar constructor:

Baz () : Foo(), Bar(this) {}

A working example:

#include <iostream>
class Bar;
class Foo {
  public:
  virtual ~Foo() {}
  virtual void parse_bar (Bar&) const = 0;
};  

class Bar {
  private:
  const Foo * parser;
  public:
  Bar (const Foo * parser_in) : parser(parser_in) {}
  virtual ~Bar() {}
  void parse_self () { parser->parse_bar (*this); }
};  

class Baz : public Foo, public Bar {
  public:
  Baz () : Foo(), Bar(this) {}
  virtual void parse_bar (Bar &) const { std::cout << "Hello World\n"; }
};

int main () {
  Baz baz;
  baz.parse_self();
}

This happens to work on my computer, with my compilers (tested with a couple of them). However section 9.3.2 of the 2003 standard makes me a bit uneasy that I might just be getting lucky, that using this this way is undefined behavior. Strictly speaking, the initializer list is outside the body of the constructor. Here's the relevant text, emphasis mine:

9.3.2 The this pointer
In the body of a nonstatic member function, the keyword this is a non-lvalue expression whose value is the address of the object for which the function is called.

So is my usage legal and well-defined, or is it undefined behavior?

David Hammen
  • 32,454
  • 9
  • 60
  • 108

2 Answers2

5

There are two points that have to be noted in this case.

Firstly, in the constructor initializer list this pointer refers to a non-constructed (or not-fully-constructed) object. It is OK to access such pointer, but the object it refers to can only be used in limited ways. See 12.7 in the language specification.

Secondly, in your specific example what you are actually doing is converting this pointer to Foo * type before attempting any access. This is completely safe since by that moment the Foo subobject is fully constructed. (I assume that, whatever access will follow, if any, will be restricted only to the fully constructed Foo subobject).

The only concern is this case is whether it is legal to convert this to Foo * type, i.e. whether the conversion process itself should succeed. The answer is: yes, in case of ordinary (non-virtual) inheritance such conversion is perfectly legal and safe (again, explicitly allowed in 12.7)

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
3

It is fine. The C++ standard actually clarifies the use of this pointers in initializer lists:

12.6.2 Initializing bases and members [class.base.init]

Paragraph 7: Names in the expression-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified. [Example:

    class X {
        int a;
        int b;
        int i;
        int j;
    public:
        const int& r;
        X(int i): r(a), b(i), i(i), j(this->i) {}
    };

initializes X::r to refer to X::a, initializes X::b with the value of the constructor parameter i, initializes X::i with the value of the constructor parameter i, and initializes X::j with the value of X::i; this takes place each time an object of class X is created. ] [Note: because the mem-initializer are evaluated in the scope of the constructor, the this pointer can be used in the expression-list of a mem-initializer to refer to the object being initialized. ]

The type of the this pointer in the initializer of Baz is in fact of type Baz. Of course, you have to be mindful of the fact that not all of the members may have been initialized. Many, if not all, compilers set at their highest warning levels (which you really should be doing anyway) will warn about you passing a this pointer to the base class.

However, it looks like you're making it more complicated that it needs to be. Why not just put the virtual function parse_bar() in the Bar class, and forget about the Foo class?

#include <iostream>

class Bar
{             
public:      
    Bar() {}      
    virtual ~Bar() {}      
    void parse_self () { parse_bar(); } 
private: // Template method pattern
    virtual void parse_bar() const = 0;         
};        

class Baz : public Bar
{      
public:      
    Baz () {}     
private: // Yes, this does override the private `parse_bar()` member!
    virtual void parse_bar() const { std::cout << "Hello World\n"; }      
};

int main ()
{ 
    Baz baz; 
    baz.parse_self(); 
} 

This does essentially the same function but with less code.

In silico
  • 51,091
  • 10
  • 150
  • 143
  • +1, nice answer, In silico. It was tough choosing which answer to select. Regarding the code I posted and your suggested fix: My code was contrived to illustrate the usage; a MWE. It doesn't look anything like the code I have to deal with. – David Hammen Jul 04 '12 at 19:05