c++ - 这是使用 std::shared_ptr<void> 的好方法吗?

标签 c++ boost c++14 shared-ptr variant

这段代码能算是好的设计吗?

它在 GCC 和 Visual Studio 中编译并运行良好。插槽对象最终变得非常小,包装整齐且易于推理。

但是,强制转换最终会使我的程序变慢多少?

如果我最终使用 boost::anyboost::variant 我仍然会把它们放在 std::shared_ptr 中,因为我确实需要它成为一个智能指针。

我正在使用 C++14。

//编辑开始

上下文:我正在构建一个解释器,类似于 lisp/ruby​​。我希望插槽对象成为一种上帝对象。我希望能够在槽对象本身内部构造和返回新的槽对象,包括浅拷贝和深拷贝。槽对象中的数据是一个指针,因为我打算在对象之间共享该数据。 slot_t 枚举器的存在主要是为了在 switch 语句中使用,它需要在插槽类之外访问,这就是它被设为全局的原因。构造函数中的 slot_t 是必需的,因为有时一个类型可以具有相同的内部表示,因此需要一种消除歧义的方法。我举的例子很仓促,确实有一些问题。

这就是我对插槽对象的设想:

4 members -> gate (variable / constant, ...), type, data and docstring.
Operator overloads -> bool, ==, !=, <, > <=, >=.
Some methods: copy, deep_copy, get_type(by returning a new slot), etc...
I think you did get the point. :D

这是我匆忙的例子:

//编辑结束

#include <iostream>
#include <memory>
#include <sstream>
#include <string>

using std::cout;
using std::endl;

enum class slot_t {
    number_t,
    string_t
};

class slot {
public:
    slot_t type;
    std::shared_ptr<void> data;
    slot(slot_t const p_type, double long const & p_data)
        : type {p_type}
        , data {std::make_shared<double long>(p_data)}
    {}
    slot(slot_t const p_type, std::string const & p_data)
        : type {p_type}
        , data {std::make_shared<std::string>(p_data)}
    {}
    std::string get_type() const {
        std::ostringstream output;
        switch (type) {
            case slot_t::string_t: output << "String: " << as<std::string>(); break;
            case slot_t::number_t: output << "Number: " << as<double long>(); break;
        }
        return output.str();
    }
    template <typename t>
    t as() const {
        return *std::static_pointer_cast<t>(data);
    }
};

int main() {
    slot hello {slot_t::number_t, 123};
    slot world {slot_t::string_t, "Hello, world!"};

    cout << hello.as<double long>() << endl;
    cout << world.as<std::string>() << endl;

    cout << hello.get_type() << endl;
    cout << world.get_type() << endl;
    return 0;
}

最佳答案

这看起来真的像是变体的工作。实际上,您所写的是一个写得不好的共享变体。

有很多问题。第一个是您将类型与 ctor 中的类型分开传递。二是你的访问代码不好。

所以一些调整:

// sink variables: take by value, move into storage:
slot(double long p_data)
    : type {slot_t::number_t}
    , data {std::make_shared<double long>(p_data)}
{}
// sink variables: take by value, move into storage:
slot(std::string p_data)
    : type {slot_t::string_t}
    , data {std::make_shared<std::string>(std::move(p_data))}
{}
// get type from the type, not from a separate variable.

// boost and std style visit function:
template<class F>
auto visit( F&& f )
-> typename std::result_of< F(int&) >::type
{
  switch(type) {
    case slot_t::string_t: return std::forward<F>(f)( as<std::string>() );
    case slot_t::number_t: return std::forward<F>(f)( as<double long>() );
  }
}
// const visit:
template<class F>
auto visit( F&& f ) const
-> typename std::result_of< F(int const&) >::type
{
  switch(type) {
    case slot_t::string_t: return std::forward<F>(f)( as<std::string>() );
    case slot_t::number_t: return std::forward<F>(f)( as<double long>() );
  }
}
// const and non-const as that return references:
template <typename t>
t const& as() const {
    return *static_cast<t const*>(data.get());
}
template <typename t>
t& as() {
    return *static_cast<t*>(data.get());
}

enum type_t 附近:

inline std::string get_typename(type_t type) {
  switch (type) {
    case type_t::string_t: return "String";
    case type_t::number_t: return "Number";
  }
}

因为名称是 type_t 的属性,而不是您的 slot 类型的属性。

get_type 的 C++14 实现,我们将访问者创建为 lambda:

std::string get_type() const {
  std::ostringstream output;
  output << get_typename(type) << ": ";
  return visit( [&](auto&& value) {
    output <<  value; // notice not casting here
  } );
  return output.str();
}

此访问模式模仿了 boost::variant 的工作方式,您应该按照这种方式设计代码。在 C++11 中,您必须单独编写访问者类,而不是作为 lambda。

当然:

struct slot {
  std::shared_ptr< boost::variant<std::string, long double> > pImpl;
};

是一个更好的 chassy。现在我们只需要将您的枚举和操作映射到对共享 ptr 内的变体进行操作即可。

但是,另一个经验法则是共享状态不好。它几乎和全局状态一样糟糕;它使程序难以推理。我会取消共享状态,并将其替换为裸变体。

int 和 long double 的复制成本都很低。包含它们的变体也很容易复制。


visit 功能强大。但有时您需要基于对象类型的完全不同的代码。这有效:

template<class...Ts>
struct overload_t {
private:
  struct never_used {};
public:
  void operator()(never_used)=delete;
};
template<class T0, class...Ts>
struct overload_t: T0, overload_t<Ts...> {
  overload_t(T0 t0, Ts...ts):
    T0(std::move(t0)),
    overload_t<Ts...>(std::move(ts)...)
  {}
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;

  using T0::operator();
  using overload_t<Ts...>::operator();
};
template<class...Fs>
overload_t<Fs...>
overload(Fs...fs) {
  return {std::move(fs)...};
}

现在我们构建重载:

auto detect_type = overload(
  [](std::string const&){ std::cout << "I am a string\n"; },
  [](long double){ std::cout << "I am a number\n"; }
};
slot.visit(detect_type);

并且重载解析开始,导致正确的类型安全函数被调用。

如果你想忽略某些类型,只需这样做:

auto detect_type = overload(
  [](std::string const&){ std::cout << "I am a string\n"; },
  [](auto const&){ std::cout << "I was ignored\n"; }
};

再一次,让重载解析解决您的问题。

将类型不安全的操作隔离到代码库的一小部分。不要强制用户在每次与您的变体类型交互时都获得完全正确的类型或产生未定义的行为。

关于c++ - 这是使用 std::shared_ptr<void> 的好方法吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40872323/

相关文章:

c++ - 删除第一行时 selectionChanged() 的行为

c++ - Boost.Hana 中是否有与 Boost.Fusion 中的 View 概念等效的概念?

C++ boost反序列化抛出错误

c++ - 从模板函数中缓存一个基类型以在 std::is_base_of<Here!,> 中使用

c++ - 哪个 C++ 编译器最符合最新的 C++ 标准

c++ - 不使用循环和数字和的数字根递归

c++ - Qt QTimeLine 跳过第一帧

c++ - 查找 cpp_int 二进制长度的简单方法

c++ - 将 `hana::string` 转换为 `constexpr const char (&)[]`

c++ - 在 C++11/C++14 中的类中存储对象类型的列表/映射