3

I have the following setup:

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

An object of A will never be exposed to any program including foo.h. The vector with A's is only there to help B in its computation adapter role. By making A's constructor private, I thought I could avoid other compilation units from using it, and it seems to work. However, the problem is in

foo.cpp

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

where part of MyCustomStorage looks like so:

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

But I thought since allocate() is called from a member function, this wouldn´t happen! How could I solve this?

Making A a friend to MyCustomStorage seems very spaghetti-codish. Making A a private nested class of B makes all sorts of help-classes in foo.cpp fail because "A is private".

So what would be the cleanest way to solve this?

SOLUTION

I ended up going with @potatoswatter 's second solution, with these appropriate changes:

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!
}
bombax
  • 1,129
  • 8
  • 22

2 Answers2

3

It's not the constructor of A that is private, it's the entire class.

The best solution is to create a "private" namespace. C++ doesn't have namespace-level access protection, but it's reasonable to expect that users won't access an unfamiliar namespace.

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

Another approach is to make A a member of B. This offers "real" access protection at the expense of deeper nesting. I personally prefer the first solution, and to avoid nested classes.

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

Any helpers that need A would then need to be friends. There are various workarounds like nesting A in a base class with protected access, but really, namespace impl offers the least compromises.

Potatoswatter
  • 126,977
  • 21
  • 238
  • 404
  • 1
    I like the second one more. It is objectively better since it encapsulates `A`, which the first version does not. I wouldn't hope for people not accessing unfamiliar namespaces, `namespace std;` once was unfamiliar and still everyone is `using` it. – nwp Aug 11 '15 at 14:23
  • @nwp But namespaces like `std::__1` and `std::__function` are unfamiliar. Large, popular libraries including all standard library implementations, Boost, etc, tend to prefer the first approach, and otherwise do not contort classes for the sake of access protection. – Potatoswatter Aug 11 '15 at 21:53
  • @potatoswatter I've used the second solution, edited in my first post. I'd love your comments on whether there's the contortion problem mentioned. – bombax Aug 12 '15 at 14:20
  • 1
    @bombax Sure, that works. If there's any "contortion," it's just that `B` is capturing additional functionality as if it were a namespace. If it becomes a problem, you can always refactor the nested classes out. – Potatoswatter Aug 12 '15 at 22:22
1

IMHO, You have a couple options. You can either 1) Use the Pimpl idiom, or, 2) you can use forward declaration.

Pimpl Idiom Example:

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

And in your *.cpp file, you can define the Impl class and use it's guts.

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

You can also use a smart pointer for the Pimpl idiom, I just didn't here for the sake of clarity/brevity.

Forward declaration can also be used if A is in the same namespace as 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;
};

But this idiom is fundamentally different than your requirements and restricts you to using a pointer within your object myMember, which you may not want to do. Inline defining class A* is also a non-standard forward-declarative approach. Of course, use of smart pointers would reduce the possibility of memory-leaks at this location.

Community
  • 1
  • 1
Tyler Jandreau
  • 4,060
  • 17
  • 40