Home > database >  Making a special constructor inaccessible to users but accessible to std::construct_at
Making a special constructor inaccessible to users but accessible to std::construct_at

Time:08-22

My code needs a special constructor that std::construct_at can use. This works fine if it's public but then it can be accessed by users of the class. If it is private, std::construct_at fails. Here's the minimal code:

#include <memory>
#include <iostream>

class Vec {
public:
    int* const p{};
    const int len{};
    Vec(int len = 1) : len(len), p(new int[len]{}) {}

    // Raw ctor, for use only inside class using std::construct_at to overlay object
    // Doesn't work executed from std::construct_at if private
    Vec(int* p, int len) : p(p), len(len) {}

    Vec& operator=(Vec&& rhs)
    {
        delete[] p;
        std::construct_at(this, rhs.p, rhs.len);    // dtor not needed for this class
        std::construct_at(&rhs, nullptr, 0);        // steal rhs guts and make valid
        return *this;
    }
    // .... Other ctors
    ~Vec() {delete[] p;}
};

int main()
{
    int* pi{};
    Vec x(pi, 0); // ctor accessable to users. Not good.
    Vec a, b(2);
    b.p[1] = 42;
    a = std::move(b);
    std::cout << "a.p[1] " << a.p[1] << " b.p (Null) " << b.p << "\n";
}

So I tried creating an empty struct tag (INNER) defined in the private space, but used in the public ctor. This works:

#include <memory>
#include <iostream>

class Vec {
    struct INNER {};
public:
    int* const p{};
    const int len{};
    Vec(int len = 1) : len(len), p(new int[len] {}) {}

    // Raw ctor, for use only inside class using std::construct_at to overlay object
    // Doesn't work executed from std::construct_at if private
    Vec(int* p, int len, struct INNER) : p(p), len(len) {}

    Vec& operator=(Vec&& rhs)
    {
        delete[] p;
        std::construct_at(this, rhs.p, rhs.len, INNER{});    // dtor not needed for this class
        std::construct_at(&rhs, nullptr, 0, INNER{});        // steal rhs guts and make valid
        return *this;
    }
    // .... Other ctors
    ~Vec() { delete[] p; }
};

int main()
{
    int* pi{};
    //Vec x(pi, 0, Vec::INNER{}); // not compilable since INNER is private
    Vec a, b(2);
    b.p[1] = 42;
    a = std::move(b);
    std::cout << "a.p[1] " << a.p[1] << " b.p (Null) " << b.p << "\n";
}

While this works in GCC, CLANG, and MSVC. But it seems odd that std::construct_at can use it since INNER is defined in the private segment. Is std::construct_ats use of INNER legit?

CodePudding user response:

There's nothing wrong about it. When you define a INNER class in the private scope of an OUTER class, the only thing you are not allowed is manually using the name of INNER class outside of of OUTER.

Assuming you have a class like:

class OUTER {
    class INNER {};
public:
    INNER create_inner() { return INNER{}; }
};

So you are not allowed to do:

int main() {
    OUTER::INNER inner{};
}

However, you can always do:

int main() {
    auto inner = OUTER{}.create_inner();
}

Note:

In your code, while user are not allowed to do Vec x(pi, 0, Vec::INNER{}) anymore, they are free to use it like Vec x(pi, 0, {}).

CodePudding user response:

But it seems odd that std::construct_at can use it since INNER is defined in the private segment. Is std::construct_ats use of INNER legit?

std::construct_at by itself only use public interface. it is legit.

passkey idiom has some caveat though, and you are misusing it.

private forbids to use the name explicitly, but you can use other form (as implicit contructor with {/*..*/} or decltype, ...) to actually use the thing which should be protected.

You have to protect a little more:

class Vec
{
    class Key
    {
        friend Vec; // Who can have the key
        constexpr KeyT() {} // Not default to avoid non contrainted uniform initialization call (< C  20)
        constexpr KeyT(const KeyT&) = default;
    };
public:
    // ...

    // "private", except for those which have the key
    constexpr Vec(int* p, int len, Key) : p(p), len(len) {}

    Vec& operator=(Vec&& rhs)
    {
        std::destroy_at(this);
        std::construct_at(this, rhs.p, rhs.len, Key{});
        std::construct_at(&rhs, nullptr, 0, Key{});
        return *this;
    }
    // .... Other ctors
    ~Vec() { delete[] p; }
};

Here, only Vec can create a Key, and it can pass it to any functions

  •  Tags:  
  • c
  • Related