Тривиальная проверка времени компиляции на отсутствие конструктора копирования - PullRequest
0 голосов
/ 15 октября 2018

Широко известная идиома создания типов без копирования состоит в создании базового класса

struct NoCopy {
   NoCopy(){}
   NoCopy(const NoCopy&) = delete;
   NoCopy& operator=(const NoCopy&) = delete;  
};

и производного от него, например,

struct Foo : NoCopy {
    Foo(){}
};

, который сделает следующеене удается скомпилировать

Foo f;
Foo f2 = f;

Но как мне обеспечить это?Любой производный класс может выполнять следующие действия:

struct Foo2 : NoCopy {
    Foo2(){}
    Foo2(const Foo2&){}
};

. Это совершенно законно, но не имеет смысла. Теперь у меня есть тип, который можно копировать и не копировать (через его базовый класс).

Как мне избежать этого?

Ответы [ 3 ]

0 голосов
/ 15 октября 2018

Если вы хотите защитить людей, случайно забывших вызвать конструктор копирования NoCopy в своем собственном (запрещенном по имени) конструкторе копирования, я бы предложил:

namespace
{
    struct NotCopyableInitT {};
}    

// You can choose whatever stern words you want here.
NotCopyableInitT initNoCopy() { return {}; }

struct NoCopy {
    explicit NoCopy(NotCopyableInitT){}
    NoCopy(const NoCopy&) = delete;
    NoCopy& operator=(const NoCopy&) = delete;  
};

Если онинастаивайте на добавлении возможности копирования, где это запрещено, вы заставляете их излагать свою собственную ошибку:

struct Foo2 : mylib::NoCopy {
    Foo2() : NoCopy(mylib::initNoCopy()) {}
    // Users have to spell out this line in order to get a copy constructor.
    // That certainly goes beyond being forgetful.
    Foo2(const Foo2&) : NoCopy(mylib::initNoCopy()) {}
};

Демо

Для пользователей с хорошим поведением это одна дополнительнаявызов функции в конструкторе NoCopy (который, по крайней мере, линтер сказал бы вам вызывать явно в любом случае).

0 голосов
/ 15 октября 2018

Вы можете просто проверить , что типы, используемые с вашим API, не могут быть скопированы:

#include <type_traits>

namespace lib {
template<class NoCopy>
inline constexpr bool copiable_v = std::disjunction<
    std::is_copy_constructible<NoCopy>,
    std::is_copy_assignable<NoCopy>
>::value;

template<class NoCopy>
struct CheckNoCopiable
{
    static_assert(
        copiable_v<NoCopy> == false,
        "Type is copy-assignable or copy-constructible."
    );
};
}

С этим инструментом ваши функции могут выглядеть следующим образом:

namespace lib {
template<class NoCopy>
void f(NoCopy&& nc)
{
    CheckNoCopiable<NoCopy>{};

    /* do whatever with nc */
}
}

Полная демонстрационная программа: http://coliru.stacked -crooked.com / a / ed0a8f5576a68554 :

struct Alice   : lib::NoCopy {}; // nice Alice
struct Bob                   {}; // nice Bob
struct Charlie : lib::NoCopy     // naughty Charly
{
    Charlie()               {};
    Charlie(Charlie const&) {};
    Charlie& operator=(const Charlie&) { return *this; }; 
};

int main()
{
   lib::f(Alice{});
   //lib::f(Bob{});     // error: static assertion failed: Type is copy-assignable or copy-constructible.
   //lib::f(Charlie{}); // error: static assertion failed: Type is copy-assignable or copy-constructible.
}
0 голосов
/ 15 октября 2018

Это C ++.В мире шаблонного метапрограммирования возможно практически все.Если мы сделаем NoCopy базу CRTP, мы можем добавить статические утверждения в ее деструктор.

template<class C>
struct NoCopy {
   NoCopy(){}
   NoCopy(const NoCopy&) = delete;
   NoCopy& operator=(const NoCopy&) = delete;
   ~NoCopy() noexcept {
       static_assert(std::is_base_of<NoCopy, C>::value, "CRTP not observed");
       static_assert(!std::is_copy_constructible<C>::value, "A non-copyable copyable class? Really?");
   }
};

Вот ваш код, адаптированный для живого примера .

Это не без цены, так как теперь класс не является тривиально разрушаемым, и как таковой ни один класс не будет производным от него.Независимо от того, является ли это приемлемым, зависит только от вас.


При дальнейшем рассмотрении, если вы предоставите только один способ инициализации вашего класса, тогда конструктор по умолчанию имеет , на который можно сослатьсяи позвонил.Таким образом, статическое утверждение можно переместить туда, и тип снова становится тривиально разрушаемым:

template<class C>
struct NoCopy {
   NoCopy() noexcept {
       static_assert(std::is_base_of<NoCopy, C>::value, "CRTP not observed");
       static_assert(!std::is_copy_constructible<C>::value, "A non-copyable copyable class? Really?");
   }
   NoCopy(const NoCopy&) = delete;
   NoCopy& operator=(const NoCopy&) = delete;
};  

Статическое утверждение срабатывает точно так же, как этот живой пример показывает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...