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

时间:2016-11-29 17:43:02

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

这段代码可以被认为是好的设计吗?

使用GCC和Visual Studio编译和工作正常。插槽对象的结构非常小巧,整齐,易于推理。

然而,演员到底会减慢多少程序?

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

我正在使用C ++ 14。

// EDITED BEGIN

上下文:我正在建立一个解释器,这是lisp / ruby​​的一部分。我希望插槽对象成为一种神对象。我希望能够在slot对象本身内构造和返回新的slot对象,包括浅拷贝和深拷贝。 slot对象中的数据是一个指针,因为我打算在对象之间共享数据。 slot_t枚举器主要用于在switch语句中使用,并且需要在slot类之外访问,这就是为什么它是全局的。需要构造函数中的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;
}

2 个答案:

答案 0 :(得分:2)

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

有很多问题。第一个是你将类型与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;
};

是一个更好的挑剔。现在我们只需要将您的枚举和操作映射到共享ptr中的变体操作。

然而,另一个经验法则是共享状态不好。它几乎和全球国家一样糟糕;它使程序难以推理。我不再使用共享状态,而是将其替换为裸体变体。

整体和长双打都很便宜。包含它们的变体也很便宜。

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"; }
};

再次,让重载解决方案解决您的问题。

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

答案 1 :(得分:0)

真的不得不质疑你的设计选择。所以你的第一个问题,&#34;这段代码可以被认为是好的设计吗?&#34;我不得不说。公共变量很糟糕,m-kay?你的班级是一个荡妇。

shared_ptr功能中创建as的费用大约是您拥有的唯一费用。您可以使用getstatic_cast而不是static_pointer_cast来摆脱它。 static_cast在编译时执行,不应对运行时产生影响。

最后,您需要配置文件来回答您的问题。其他任何事情都是假设。