1

Defining the classes A with private constructor and destructor (it should be so!) and B as a friend class, how can I creat a vector of A objects in B and fill it with the function addA(). I got the error "error C2248: "A::~A": No access to private members whose declaration was made in the A class".

class A
{
private:
    A();
    A(const std::string& name, const float& num);
    ~A();
public:
    friend class B;
private:
    std::string name_;
    float num_;
};

A::A() 
{
    name_ = "NoName";
    num_ = 0.0;
}
A::A(const std::string& name, const float& num)
{
    name_ = name;
    num_ = num;
}
A::~A()
{   
}

class B
{
public:
    B();
    ~B();
    void addA(const std::string name, const float num);
private:
    vector<A> vecA;
};

B::B() 
{
}
B::~B() 
{
}
void B::addA(const std::string name, const float num)
{
    A a(name, num);
    vecA.push_back(a);
}

int main()
{
    B b;
    b.addA("Name", 1.0);

    return 0;
}
Fureeish
  • 12,533
  • 4
  • 32
  • 62
Hossein
  • 33
  • 3

3 Answers3

1

While @Fureeish has a neat solution, here's a slightly simpler alternative: just wrap it.

class AccessPrivate;

class PrivStuff
{
private:
    PrivStuff() {}
    ~PrivStuff() {}
public:
    friend class AccessPrivate;

    std::string m_data{};
};

class AccessPrivate
{
public:
    AccessPrivate() = default;
    ~AccessPrivate() = default;

    PrivStuff m_priv;
};

int main(int argc, char* argv[])
{
    std::vector<AccessPrivate> myvec;
    myvec.resize(4);

    for (auto& stuff : myvec)
    {
        stuff.m_priv.m_data = "heya";
    }

}

If you need something more complicated, like passing in arguments, just add an equivalent constructor to AccessPrivate and there you go. You can essentially treat AccessPrivate almost like the actual private class, just one level of indirection.

Kevin Anderson
  • 6,850
  • 4
  • 32
  • 54
  • I employed the idea of @Fureeish! thank you by the way :) – Hossein Feb 19 '20 at 19:11
  • This answer is much better, it does not introduce a very complicated indirection, to not talk about the cratering performance of `unique_ptr`! – TheCppZoo Feb 21 '20 at 01:38
0

how can I create a vector of A objects in B [...] ?

You can't do that. While B is a friend of A, std::vector is not a friend of A, which means that it cannot access private members of A, e.g., constructor, which is required for a vector to work.

However, if you are okay with a little indirection, little potential performance hit and a change in your signature, you can replace the not-working std::vector<A> with a workig std::vector<std::unique_ptr<A, deleter>>.

It's important to note that plain std::unique_ptr will not work here. It has a similar problem to std::vector - it cannot access private destructor of A. One way to work around it is to outsource the job of constructing and destructing of As entirely to B - via explicit construction and destruction, that is:

  • new A(name, num)
  • static void deleter_a(A* a) { delete a; }

in B's scope.

Now we can do:

std::vector<std::unique_ptr<A, std::function<void(A*)>>> vecA;

instead of: std::vector<A> or std::vector<std::unique_ptr<A>>. This is important - neither std::unique_ptr nor std::vector construct or destruct your As. B is entirely responsible for constructing (new A(name, num)) and destructing (static void deleter_a(A* a) { delete a; }) As.

Full B class:

class B {
public:
    B() {};  // or = default
    ~B() {}; // or = default
    void addA(const std::string name, const float num);

private:
    static void deleter_a(A* a) { delete a; }
    using deleter_a_t = void(A*);
    std::vector<std::unique_ptr<A, std::function<deleter_a_t>>> vecA;
};

void B::addA(const std::string name, const float num) {
    vecA.push_back(std::unique_ptr<A, std::function<deleter_a_t>>{
            new A(name, num), std::function<deleter_a_t>{deleter_a}
    });
}
Fureeish
  • 12,533
  • 4
  • 32
  • 62
0

Contrary to what the other answers say, it is possible to do this without any extra indirection.

std::vector doesn't directly call the constructor and the destructor, but uses an allocator. If you want an std::vector to manage A objects, you just need to provide it an allocator that implements the construct and destroy functions, and that is either a friend of A or a nested class of B (since B is already a friend of A).

Example:

#include <memory>
#include <utility>
#include <vector>

class A {

    A() = default;
    ~A() = default;

    friend class B;
    
};

class B {
    
    template<typename T>
    struct custom_alloc : std::allocator<T> {
        
        template<typename U, typename... Args>
        void construct(U* p, Args&&... args){
            ::new(const_cast<void*>(static_cast<const volatile void*>(p))) U(std::forward<Args>(args)...);
        }
        
        template<typename U>
        void destroy(U* p){
            if constexpr (std::is_array_v<U>){
                for(auto& elem : *p){
                    (destroy)(std::addressof(elem));
                }
            } else {
                p->~U();
            }
        }
        
    };
    
public:

    std::vector<A,custom_alloc<A>> vec;
    
    void new_A(){
        vec.push_back(A());
    }
    
};

For the implementation of construct and destroy, I used an equivalent implementation of the c++20 versions of std::destroy_at and std::construct_at. I suspect that destroy is overkill and just a call to the destructor would be sufficient, but I'm not sure.

ThePirate42
  • 821
  • 6
  • 22