2012-07-04 3 views
2

Предположим, у меня есть класс Baz, который наследуется от классов Foo и Bar, в этом порядке. Конструктор для класса Bar принимает указатель на объект Foo. То, что я хотел бы сделать, это передать this как Foo объекта к Bar конструктора:Использование этого в списке инициализаторов

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

Рабочему пример:

#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(); 
} 

Это происходит работать на моем компьютере, с моими составителями (испытанными с несколькими из них). Однако в разделе 9.3.2 стандарта 2003 года мне немного неловко, что мне просто повезет, что использование таким образом является неопределенным поведением. Строго говоря, список инициализаторов находится вне тела конструктора. Вот соответствующий текст, курсив мой:

9.3.2 this указателя
В теле нестатического функции члена, ключевое слово this не является именующим выражением, значение которого является адресом объекта для которого вызывается функция.

. Мое использование является законным и четко определенным, или это неопределенное поведение?

ответ

4

В этом случае необходимо отметить два момента.

Во-первых, в списке инициализатора конструктора this указатель ссылается на нестроенный (или не полностью сконструированный) объект. Доступ к такому указателю возможен, но объект, на который он ссылается, может использоваться только ограниченным образом. См. 12.7 в спецификации языка.

Во-вторых, в вашем конкретном примере то, что вы на самом деле делаете, преобразует указатель this в тип Foo *, прежде чем пытаться получить доступ. Это абсолютно безопасно, так как к этому моменту полностью создан подобъект Foo. (Я предполагаю, что любой доступ будет следовать, если таковой имеется, будет ограничен только полностью построенным подобъектом Foo).

Единственная проблема заключается в том, является ли законным преобразовывать this в тип Foo *, то есть сам ли процесс преобразования должен быть успешным. Ответ: да, в случае обычного (не виртуального) наследования такое преобразование совершенно законно и безопасно (опять же, явно разрешено в 12.7)

+0

Гораздо яснее, чем мой ответ, который я немного удалю. – GManNickG

+0

@GManNickG Dont! Комментарии могут быть полезны для новичков – TeaOverflow

+0

@Evgeni: Извините. :) Новички! Вопрос о наследовании класса? -> [Список книг] (http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). – GManNickG

3

Все в порядке. Стандарт C++ на самом деле проясняет использование this указателей в списках инициализаторов:

12.6.2 Инициализация базы и членов [class.base.init]

Пункт 7: Имена в expression-list из a mem-initializerоцениваются в рамках конструктора, для которого указан mem-initializer. [Пример:

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) {} 
    }; 

инициализирует X::r обратиться к X::a, инициализирует X::b с значения параметра конструктора i, инициализирует X::i с значения параметра конструктора i и инициализирует X::j с в значение X::i; это происходит каждый раз, когда создается объект класса X . ] [Примечание: поскольку mem-initializer оцениваются в , сфера применения конструктора, то this указатель может быть использован в выражения-списка в mem-initializer для обозначения объекта инициализирован. ]

Типом this указателя в инициализаторе Baz фактически типа Baz. Конечно, вы должны помнить о том, что не все члены, возможно, были инициализированы.Многие, если не все, компиляторы, установленные на своих наивысших уровнях предупреждения (которые вы действительно должны делать в любом случае), будут предупреждать о том, что вы передаете указатель базовому классу.

Однако, похоже, что вы делаете это более сложным, чем это должно быть. Почему бы просто не поместить виртуальную функцию parse_bar() в класс Bar, и забыть о классе Foo?

#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(); 
} 

Это по существу то же самое, но с меньшим количеством кода.

+0

+1, хороший ответ, в силиконе. Было сложно выбрать, какой ответ выбрать. Что касается кода, который я опубликовал, и предлагаемого исправления: мой код был изобретен для иллюстрации использования; MWE. Это не похоже на код, с которым мне приходится иметь дело. –

 Смежные вопросы

  • Нет связанных вопросов^_^