Hossein
Hossein

Reputation: 33

Vector of class object with private constructor and destructor?

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

Upvotes: 1

Views: 887

Answers (3)

ThePirate42
ThePirate42

Reputation: 890

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.

Upvotes: 0

Kevin Anderson
Kevin Anderson

Reputation: 7010

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.

Upvotes: 1

Fureeish
Fureeish

Reputation: 13434

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

Upvotes: 0

Related Questions