C++: не могу понять, как правильно скрыть детали реализации

У меня есть следующие настройки:

foo.h:

class A {
    friend class B;
private:
    A() {}
};

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    vector<A> myMember;
};

Объект A никогда не будет подвергаться какой-либо программе, включая foo.h, Вектор с Aтолько там, чтобы помочь B в роли адаптера вычислений. Делая AЭто частный конструктор, я думал, что смогу избежать его использования другими модулями компиляции, и это похоже на работу. Однако проблема заключается в

foo.cpp

void B::computeResult(Result &r) {
    MyCustomStorage<A> storage;
    A *a = storage.allocate(); // error: "A::A() is private"
}

где часть MyCustomStorage выглядит так:

template <typename T>
class MyCustomStorage {
    T *allocate() {
        ...
        T *ptr = new T[count]; // error: "A::A() is private"
        ...
    }
};

Но я так и думал allocate() вызывается из функции-члена, этого не произойдет! Как я мог решить это?

Изготовление A друг MyCustomStorage кажется очень спагетти-треска. Изготовление A частный вложенный класс B делает все виды справочных классов в foo.cpp потерпеть неудачу, потому что "А является частным".

Итак, что будет самым чистым способом решить эту проблему?

РЕШЕНИЕ

В итоге я выбрал второе решение @potatoswatter со следующими соответствующими изменениями:

foo.h

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    class A {
    private:
        A() {}
    };
    class Helper; // forward declared!
    vector<A> myMember;
};

foo.cpp

class B::Helper {
    int help(A& a) { return 42; } // no problem! Helper is a member of B
}

void B::computeResult(Result &r) {
    MyCustomStorage<A> storage;
    A *a = storage.allocate(); // no problem! A is a member of B
    Helper h;
    h.help(*a); // no problem!
}

2 ответа

Решение

Это не конструктор A это личное, это весь класс.

Лучшее решение - создать "личное" пространство имен. C++ не имеет защиты доступа на уровне пространства имен, но разумно ожидать, что пользователи не получат доступ к незнакомому пространству имен.

namespace impl {
struct A {
    A() {}
};
}

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    vector<impl::A> myMember;
};

Другой подход заключается в том, чтобы сделать A член B, Это обеспечивает "настоящую" защиту доступа за счет более глубокого вложения. Я лично предпочитаю первое решение и избегать вложенных занятий.

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    struct A {
        A() {}
    };

    vector<A> myMember;
};

Любые помощники, которые нуждаются A тогда нужно будет дружить. Существуют различные обходные пути, такие как вложение A в базовом классе с protected доступ, но на самом деле, namespace impl предлагает наименьшее количество компромиссов.

ИМХО, у вас есть пара вариантов. Вы можете либо: 1) использовать идиому Pimpl, либо 2) вы можете использовать предварительную декларацию.

Пример идиома Pimpl:

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    class Impl;
    Impl *pimpl;
};

И в вашем *.cpp файле вы можете определить Impl класс и использовать его мужество.

class B::Impl {
public:
    std::vector<A> stuff;
}

B::B() : pimpl(new Impl) {
}

B::~B() {
    delete pimpl;
}

void B::AddObject(Object &o) {
    pimpl->stuff.Fx(o);
}

Вы также можете использовать умный указатель для идиомы Pimpl, я просто не здесь для ясности / краткости.

Форвардная декларация также может быть использована, если A находится в том же пространстве имен, что и B

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    std::vector<class A*> myMember;
};

Но эта идиома принципиально отличается от ваших требований и ограничивает вас использованием указателя внутри вашего объекта myMember что вы, возможно, не хотите делать. Встроенное определение class A* это также нестандартный форвард-декларативный подход. Конечно, использование интеллектуальных указателей уменьшит вероятность утечек памяти в этом месте.

Другие вопросы по тегам