Pimpl, предварительное объявление частного класса, оператор разрешения области видимости

Рассмотрим эти два класса, в которых используется идиома Pimpl:

ClassA: предварительное объявление класса Pimpl и объявление переменной в отдельных строках

ClassA.h:

#include <memory>

class ClassA {
public:
    ClassA();
    ~ClassA();
    void SetValue( int value );
    int GetValue() const;

private:

    class ClassA_Impl;
    // ^^^^^^^^^^^^^^ class forward declaration on its own line

    std::unique_ptr<ClassA_Impl> m_pImpl;
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ variable declaration on its own line

    //EDIT:
    //Even if we use a raw pointer instead of a smart pointer,
    //i.e. instead of declaring the smart pointer above, if we declare:
    ClassA_Impl *m_pImpl;
    //the situation in the *.cpp files and my questions (below) are the same.


};

ClassA.cpp:

#include "ClassA.h"

class ClassA::ClassA_Impl {
public:
    void SetValue( int value );
    int GetValue() const;
private:
    int value_;   
};

// Private class implementation
void
ClassA::ClassA_Impl::SetValue( int value ) {
    value_ = value;
}

int
ClassA::ClassA_Impl::GetValue() const {
    return value_;
}

// ClassA implementation
ClassA::ClassA() : m_pImpl( new ClassA_Impl() ) {}

ClassA::~ClassA() {}

void
ClassA::SetValue( int value ) {
    m_pImpl->SetValue( value );
}

int
ClassA::GetValue() const {
    return m_pImpl->GetValue();
}

ClassB: предварительное объявление класса Pimpl и объявление переменной в одной строке

ClassB.h:

#include <memory>

class ClassB {
public:
    ClassB();
    ~ClassB();
    void SetValue( int value );
    int GetValue() const;

    private:
        std::unique_ptr<class ClassB_Impl> m_pImpl;
        //             ^^^^^^^^^^^^^^^^^^ class forward declaration
        //             combined with variable declaration on one line,
        //             in one shot.

        //EDIT:
        //Even if we use a raw pointer instead of a smart pointer,
        //i.e. instead of declaring the smart pointer above, if we declare:
        class ClassB_Impl *m_pImpl;
        //the situation in the *.cpp files and my questions (below) are the same.
};

ClassB.cpp:

#include "ClassB.h"

class ClassB_Impl {
public:
    void SetValue( int value );
    int GetValue() const;
private:
    int value_;
};

// Private class implementation
void
ClassB_Impl::SetValue( int value ) {
    value_ = value;
}

int
ClassB_Impl::GetValue() const {
    return value_;
}

// ClassB implementation
ClassB::ClassB() : m_pImpl( new ClassB_Impl() ) {}

ClassB::~ClassB() {}

void
ClassB::SetValue( int nValue ) {
    m_pImpl->SetValue( nValue );
}

int
ClassB::GetValue() const {
    return m_pImpl->GetValue();
}

Вопросы:

  1. Почему объединение прямого объявления и объявления переменной в одной строке в ClassB.h требует, чтобы ClassB_Impl был "не задан" в реализации частного класса в ClassB.cpp?

    то есть в ClassA.cpp определения методов частного класса начинаются с

    void ClassA::ClassA_Impl::foo() {...
    

    но в ClassB.cpp определения методов частного класса начинаются с

    void ClassB_Impl::foo() {...
    
  2. Каковы последствия каждого метода? Какой лучше?

  3. (Дополнительный вопрос в ответ на ответ Галика)

    Когда вы объединяете прямое объявление класса и объявление переменной этого класса в одном операторе ...

    //one-liner
    class ClassB_Impl *m_pImpl;
    

    ...как это называется? Есть ли название для такого комбинированного утверждения? И почему именно ClassB_Impl не становится внутренним классом ClassB в результате такого утверждения?

    Сравните это с ...

    //two-liner
    class ClassA_Impl;
    ClassA_Impl *m_pImpl;
    

    ... в этом случае ClassA_Impl действительно становится внутренним классом ClassA.

    Почему однострочник помещает ClassB_Impl в глобальное пространство имен, а двухстрочный помещает ClassA_Impl в пространство имен ClassA? Почему они разные?


person Quokka    schedule 08.02.2016    source источник


Ответы (1)


Почему объединение прямого объявления и объявления переменной в одной строке в ClassB.h требует, чтобы ClassB_Impl был «не задан» при реализации частного класса в ClassB.cpp?

Потому что в первом примере вы объявляете ClassA_Impl как внутренний класс для ClassA.

Когда вы объявляете ClassB_Impl в параметре шаблона, который не является частью ClassB.

Каковы последствия каждого метода? Какой лучше?

Это вопрос мнения. Лично я считаю, что внутренние классы беспорядочные, и с ними труднее работать за небольшое вознаграждение.

В моем предпочтительном методе используется отдельный класс interface, который помогает сократить количество повторных объявлений интерфейса.

См. Можно ли написать гибкий Pimpl на C ++?

person Galik    schedule 08.02.2016
comment
Когда вы объявляете его в параметре шаблона, который не является частью ClassB. - Я отредактировал вопрос, чтобы отразить, что даже если я использую необработанный указатель вместо интеллектуального указателя (чтобы частный класс не объявлялся в параметре шаблона), ситуация остается той же. Значит, даже с необработанным указателем означает ли это, что объявление типа class ClassB_Impl *m_pImpl делает ClassB_Impl не частью класса? Означает ли это, что ClassB_Impl доступен в глобальном пространстве имен? - person Quokka; 08.02.2016
comment
Размышляя об этом подробнее, я понимаю, что ClassB_Impl находится в глобальном пространстве имен; так и должно быть, поэтому в ClassB.cpp мы можем (фактически, мы должны) сказать void ClassB_Impl::foo() {... вместо void ClassB::ClassB_Impl::foo() {.... Если это действительно так, то я думаю, что ответ на мой вопрос 2 состоит в том, что ClassA лучше, потому что внутренний класс Pimpl остается скрытым; в отличие от ClassB, ClassA_Impl недоступен в глобальном пространстве имен, что мы предпочли бы. Мысли? Я что-нибудь упускаю? - person Quokka; 08.02.2016
comment
@Quokka Я думаю, ты уже все понял. Единственный способ сделать класс внутренним классом ClassB - это объявить его как класс внутри ClassB. Этого не происходит, когда вы просто объявляете тип одной из переменных-членов ClassB. - person Galik; 08.02.2016