有条件地禁用复制构造函数 [英] Conditionally disabling a copy constructor

查看:129
本文介绍了有条件地禁用复制构造函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我正在写一个拥有 T 值的类模板 C< T> ,所以<$只有当 T 是可复制的时,才可以复制c $ c> C 。通常,当模板可能支持或可能不支持某个操作时,您只需定义该操作,并由调用方来避免在不安全的情况下调用它:

  template< typename T> 
class C {
private:
T t;

public:
C(const C& rhs);
C(C&& rhs);

//其他东西
};但是,这在复制构造函数的情况下会产生问题,因为 is_copy_constructible< pre

将是真的,即使 T 不可复制; trait不能看到拷贝构造函数如果被调用将是不成形的。并且这是一个问题,因为例如向量有时会避免使用move构造函数,如果 std :: is_copy_constructible 是true。如何解决这个问题?



我相信 is_copy_constructible 会做正确的事情,如果构造函数显式或隐式默认:

  template< typename T> 
class C {
private:
T t;

public:
C(const C& rhs)= default;
C(C&& rhs)= default;

//其他资料
};

然而,并不总是可以构造你的类,所以默认的构造函数会做正确的事情。 / p>

我可以看到的另一种方法是使用SFINAE有条件地禁用复制构造函数:

 code> template< typename T> 
class C {
private:
T t;

public:
template< typename U = C>
C(typename std :: enable_if< std :: is_copy_constructible< T> :: value,
const U&> :: type rhs);
C(C&& rhs);

//其他东西
};

除了丑陋的罪恶之外,这种方法的麻烦是我必须使构造函数模板,因为SFINAE只适用于模板。根据定义,复制构造函数不是模板,所以我禁用/启用的东西实际上不是复制构造函数,因此它不会抑制编译器隐式提供的复制构造函数。



我可以通过显式删除复制构造函数来解决这个问题:

  template< typename T& 
class C {
private:
T t;

public:
template< typename U = C>
C(typename std :: enable_if< std :: is_copy_constructible< T> :: value,
const U&> :: types rhs);
C(const C&)= delete;
C(C&& rhs);

//其他资料
};

但是,仍然不能防止在重载解析过程中考虑拷贝构造函数。这是一个问题,因为所有其他是相等的,普通的函数将在重载解析中击败一个函数模板,所以当你尝试复制一个 C 构造函数被选中,即使 T 是可复制的,也会导致构建失败。



原则上将工作是从主模板中省略复制构造函数,并在部分专门化中提供它(使用更多的SFINAE技巧在T不可复制时禁用它)。但是,这是脆弱的,因为它需要我复制 C 的整个定义,这会产生两个副本不同步的主要风险。我可以通过让方法体共享代码来缓解这种情况,但是我仍然必须重复类定义和构造函数成员初始化列表,这是有足够的空间让错误潜入。我可以通过让它们继承从共同的基类,但引入继承可以有各种不受欢迎的后果。此外,公共继承似乎是工作的错误工具,当我想做的是禁用一个构造函数。



有没有更好的选择,我避免'一个值得注意的方法是周围类模板的部分专门化。


$ b

$

$ b

 模板< typename T,
bool = std :: is_copy_constructible< T> :: value&
struct Foo
{
T t;

Foo(){/ * ... * /}
Foo(Foo const& other):t(other.t){/ * ... * /}
};

template< typename T>
struct Foo< T,false> :Foo< T,true>
{
使用Foo< T,true> :: Foo;

//现在删除这个特化的拷贝构造函数:
Foo(Foo const&)= delete;

//这些定义适应Foo< T,true>中提供的内容:
Foo(Foo&&)= default;
Foo& operator =(Foo&&)= default;
Foo& operator =(Foo const&)= default;
};

这样,trait is_copy_constructible T is_copy_constructible


Suppose I'm writing a class template C<T> that holds a T value, so C<T> can be copyable only if T is copyable. Normally, when a template might or might not support a certain operation, you just define the operation, and it's up to your callers to avoid calling it when it's not safe:

template <typename T>
class C {
 private:
  T t;

 public:
  C(const C& rhs);
  C(C&& rhs);

  // other stuff
};

However, this creates problems in the case of a copy constructor, because is_copy_constructible<C<T>> will be true even when T is not copyable; the trait can't see that the copy constructor will be ill-formed if it's called. And that's a problem because, for example, vector will sometimes avoid using the move constructor if std::is_copy_constructible is true. How can I fix this?

I believe is_copy_constructible will do the right thing if the constructor is explicitly or implicitly defaulted:

template <typename T>
class C {
 private:
  T t;

 public:
  C(const C& rhs) = default;
  C(C&& rhs) = default;

  // other stuff
};

However, it's not always possible to structure your class so that defaulted constructors will do the right thing.

The other approach I can see is to use SFINAE to conditionally disable the copy constructor:

template <typename T>
class C {
 private:
  T t;

 public:
  template <typename U = C>
  C(typename std::enable_if<std::is_copy_constructible<T>::value,
                            const U&>::type rhs);
  C(C&& rhs);

  // other stuff
};

Aside from being ugly as sin, the trouble with this approach is that I have to make the constructor a template, because SFINAE only works on templates. By definition, copy constructors are not templates, so the thing I'm disabling/enabling isn't actually the copy constructor, and consequently it won't suppress the copy constructor that's implicitly provided by the compiler.

I can fix this by explicitly deleting the copy constructor:

template <typename T>
class C {
 private:
  T t;

 public:
  template <typename U = C>
  C(typename std::enable_if<std::is_copy_constructible<T>::value,
                            const U&>::type rhs);
  C(const C&) = delete;
  C(C&& rhs);

  // other stuff
};

But that still doesn't prevent the copy constructor from being considered during overload resolution. And that's a problem because all else being equal, an ordinary function will beat a function template in overload resolution, so when you try to copy a C<T>, the ordinary copy constructor gets selected, leading to a build failure even if T is copyable.

The only approach I can find that in principle will work is to omit the copy constructor from the primary template, and provide it in a partial specialization (using more SFINAE trickery to disable it when T is not copyable). However, this is brittle, because it requires me to duplicate the entire definition of C, which creates a major risk that the two copies will fall out of sync. I can mitigate this by having the method bodies share code, but I still have to duplicate the class definitions and the constructor member-init lists, and that's plenty of room for bugs to sneak in. I can mitigate this further by having them both inherit from a common base class, but introducing inheritance can have a variety of unwelcome consequences. Furthermore, public inheritance just seems like the wrong tool for the job when all I'm trying to do is disable one constructor.

Are there any better options that I haven't considered?

解决方案

A noteworthy approach is partial specialization of the surrounding class template.

template <typename T,
          bool = std::is_copy_constructible<T>::value>
struct Foo
{
    T t;

    Foo() { /* ... */ }
    Foo(Foo const& other) : t(other.t) { /* ... */ }
};

template <typename T>
struct Foo<T, false> : Foo<T, true>
{
    using Foo<T, true>::Foo;

    // Now delete the copy constructor for this specialization:
    Foo(Foo const&) = delete;

    // These definitions adapt to what is provided in Foo<T, true>:
    Foo(Foo&&) = default;
    Foo& operator=(Foo&&) = default;
    Foo& operator=(Foo const&) = default;
};

This way the trait is_copy_constructible is satisfied exactly where T is_copy_constructible.

这篇关于有条件地禁用复制构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆