将 std::unique_ptr 的子类与 std::variant 一起使用

Posted

技术标签:

【中文标题】将 std::unique_ptr 的子类与 std::variant 一起使用【英文标题】:Using subclass of std::unique_ptr with std::variant 【发布时间】:2019-03-21 08:06:17 【问题描述】:

我有一个std::unique_ptr 的子类,我正在尝试将它与std::variant 一起使用。我有以下设置

// main.cc
#include <iostream>
#include <variant>

using namespace std;

class Content 
  public:
    Content() = default;
    ~Content()  cout << "deconstructing Content" << endl; ;

    int value = 10;
;

template<typename T>
class Wrapper : public unique_ptr<T> 
  public:
    Wrapper(T *value): unique_ptr<T>(value) ;
    ~Wrapper()  cout << "deconstructing Wrapper" << endl; ;
;

static variant<Wrapper<Content>, int> do_sth(bool flag) 
  if (flag) return Wrapper(new Content());
  return 1;


int main(int argc, const char *argv[]) 
  auto result = do_sth(true);
  if (auto wrapper = get_if<Wrapper<Content>>(&result)) 
    cout << wrapper->get()->value << endl;
   else 
    cout << *get_if<int>(&result) << endl;
  

  return 0;

使用 Xcode 10.1 在 macOS 10.14 上编译

$ #c++ --version -> Apple LLVM version 10.0.0 (clang-1000.11.45.5)
$ c++ -std=gnu++17 main.cc

编译器抱怨如下

main.cc:25:12: error: no viable conversion from returned value of type 'Wrapper<Content>' to function return type 'variant<Wrapper<Content>, int>'
    return Wrapper(new Content());
           ^~~~~~~~~~~~~~~~~~~~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/variant:1142:3: note: candidate constructor not viable: no
      known conversion from 'Wrapper<Content>' to 'const std::__1::variant<Wrapper<Content>, int> &' for 1st argument
  variant(const variant&) = default;
  ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/variant:1155:13: note: candidate template ignored:
      substitution failure [with _Arg = Wrapper<Content>, $1 = 0, $2 = 0, $3 = 0]: no type named 'type' in
      'std::__1::result_of<std::__1::__variant_detail::__overload<Wrapper<Content>, int> (Wrapper<Content> &&)>'
  constexpr variant(_Arg&& __arg) noexcept(
            ^
1 error generated.

我有两个问题:首先,我做错了什么?其次,当我删除Wrapper的解构函数时,即,

template<typename T>
class Wrapper : public unique_ptr<T> 
  public:
    Wrapper(T *value): unique_ptr<T>(value) ;
;

然后编译并运行,输出如下

10
deconstructing Content

为什么它(似乎?)在没有解构器的情况下工作?

【问题讨论】:

“我做错了什么?”子类化unique_ptr 是一个主要的危险信号。无关get_if 错过了std::variant 的重点 @Caleth 如您所见,我对 C++ 非常陌生。您能否解释一下或指向一个链接,为什么子类化unique_ptr 不是一个好习惯?谢谢。 继承是钝器。其他选项通常更合适。如果一个类没有受保护的成员,它可能不应该被子类化。在这种情况下,您可以将std::unique_ptrDeleter 参数更改为打印消息的参数 【参考方案1】:

因为Wrapper继承自unique_ptr这个类的实例只能移动。

您已经为 Wrapper 定义了析构函数,因此移动操作(构造函数和赋值运算符)被删除 - 当编译器生成移动操作时,您可以阅读 here。

你可以:

1) 移除 Wrapper 的析构函数,然后编译器生成默认的移动操作

2) 添加移动操作

Wrapper(T *value): unique_ptr<T>(value) ;
Wrapper(Wrapper&&) = default; // added
~Wrapper()  cout << "deconstructing Wrapper" << endl; ;

【讨论】:

是因为它是“用户提供的”吗?还是只是“定义”?我很惊讶 =default'ing 析构函数仍然无法编译 @xaxxon 默认移动操作在析构函数是用户声明时禁用,用户声明default/delete出现在成员/析构函数时, user-provided 是添加方法定义的时间。

以上是关于将 std::unique_ptr 的子类与 std::variant 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

std :: list可以包含不同的std :: unique_ptr ?

与 std::unique_ptr 相关的错误

为啥 std::unique_ptr 重置与赋值不同?

将 const std::unique_ptr 用于 pimpl 习惯用法

使用 std::unique_ptr.reset() 更改指针下的对象

如何将 std::unique_ptr 初始化为引用