用shared_ptr替换char * <char>

时间:2016-03-14 09:08:51

标签: c++ pointers shared-ptr

我有一个结构如下:

struct P
{
    enum {INT, FLOAT, BOOLEAN, STRING, ERROR} tag;
    union
    {
        int a;
        float b;
        bool c;
        const char* d;
    };
};

我使用谷物库来序列化这个并且谷物不支持原始指针。我用const char* d替换const shared_ptr<char> d。我面临3个问题:

  1. 将char *转换为shared_ptr:

    char* x = //first element of char array
    d = shared_ptr<char> (x); // is this the right way?
    
  2. 处理分配如:

    string s = "hello";
    d = s.c_str(); // how to convert the c_str() to shared_ptr<char>?
    
  3. 根据我的阅读,shared_ptr似乎处理与原始指针非常不同的指针。我是否可以安全地使用此shared_ptr作为字符数组而没有任何副作用?

2 个答案:

答案 0 :(得分:2)

首先要说的是你正在使用工会。 c ++中的联盟真的很难做对。你真的需要一个工会吗?

如果确实需要联盟,请改用boost::variant。它为您解决了所有复杂问题。

接下来,我们使用C ++ - 而不是C.让我们这样做。摆脱那个const char *。这是地雷。这就是为什么谷物不支持它。他们做的是正确的。用它取而代之。一个std::string

编辑:

行。你自找的。这是一个使用区别联合的解决方案。

现在,请记住我说工会很难在c ++中正确?

在过去的15年(20年)里,我几乎每天都在写c ++。我是标准进展的狂热追随者,我总是使用最新的工具,我要求团队中的人员知道里面的语言和标准库......我还不确定这个解决方案是否完全强大的。我需要花一天的时间来编写测试真的确定...因为受歧视的工会非常难以才能做对。

EDIT2:

修复了'const char *'构造的bug(告诉你这很难......)

确定您不想使用boost::variant

没有?那好吧:

#include <iostream>
#include <string>

struct error_type {};
static constexpr error_type as_error = error_type {};

struct P
{
    enum {
        INT, FLOAT, BOOLEAN, STRING, ERROR
    } _tag;

    union data
    {
        data() {}
        ~data() {} // define a destructor that does nothing. We need to handle destruction cleanly in P

        int a;
        double b;   // use doubles - all calculation are performed using doubles anyway
        bool c = false;  // provide a default constructor
        std::string d;  // string or error
    } _data;

    // default constructor - we must initialised the union and the tag.

    P() : _tag { BOOLEAN }, _data {} {};

    // offer constructors in terms of the various data types we're storing. We'll need to descriminate
    // between strings and errors...

    P(int a) : _tag (INT) {
        _data.a = a;
    }

    P(double b) : _tag (FLOAT) {
        _data.b = b;
    }

    P(bool c) : _tag (BOOLEAN) {
        _data.c = c;
    }

    P(std::string s) : _tag(STRING)
    {
        new (std::addressof(_data.d)) std::string(std::move(s));
    }

    // provide a const char* constructor... because const char* converts to bool
    // more readily than it does to std::string (!!!)
    P(const char* s) : P(std::string(s)) {}

    P(std::string s, error_type) : _tag(ERROR)
    {
        new (std::addressof(_data.d)) std::string(std::move(s));
    }

    // destructor - we *must* handle the case where the union contains a string
    ~P() {
        destruct();
    }

    // copy constructor - we must initialise the union correctly

    P(const P& r)
    : _tag(r._tag)
    {
        copy_construct(r._data);
    }

    // move constructor - this will be particularly useful later...

    P(P&& r) noexcept
    : _tag(r._tag)
    {
        steal_construct(std::move(r._data));
    }

    // assignment operator in terms of constructor
    P& operator=(const P& p)
    {
        // this line can throw
        P tmp(p);

        // but these lines will not
        destruct();
        steal_construct(std::move(tmp._data));
        return *this;
    }

    // move-assignment in terms of noexcept functions. Therefore noexcept
    P& operator==(P&& r) noexcept
    {
        destruct();
        _tag = r._tag;
        steal_construct(std::move(r._data));
        return *this;
    }

    // don't define swap - we have a nothrow move-assignment operator and a nothrow
    // move constructor so std::swap will be optimal.

private:

    // destruct our union, using our tag as the type switch
    void destruct() noexcept
    {
        using namespace std;
        switch (_tag) {
            case STRING:
            case ERROR:
                _data.d.~string();
            default:
                break;
        }
    }

    /// construct our union from another union based on our tag
    void steal_construct(data&& rd) noexcept
    {
        switch(_tag) {
            case INT:
                _data.a = rd.a;
                break;
            case FLOAT:
                _data.b = rd.b;
                break;
            case BOOLEAN:
                _data.c = rd.c;
                break;
            case STRING:
            case ERROR:
                new (std::addressof(_data.d)) std::string(std::move(rd.d));
                break;
        }
    }

    // copy the other union's data based on our tag. This can throw.
    void copy_construct(const data& rd)
    {
        switch(_tag) {
            case INT:
                _data.a = rd.a;
                break;
            case FLOAT:
                _data.b = rd.b;
                break;
            case BOOLEAN:
                _data.c = rd.c;
                break;
            case STRING:
            case ERROR:
                new (std::addressof(_data.d)) std::string(rd.d);
                break;
        }
    }

public:

    // finally, now all that union boilerplate malarkey is dealt with, we can add some functionality...

    std::string report() const {
        using namespace std::string_literals;
        using std::to_string;

        switch (_tag)
        {
            case INT:
                return "I am an int: "s + to_string(_data.a);
            case FLOAT:
                return "I am a float: "s + to_string(_data.b);
            case BOOLEAN:
                return "I am a boolean: "s + (_data.c ? "true"s : "false"s);
            case STRING:
                return "I am a string: "s + _data.d;
            case ERROR:
                return "I am an error: "s + _data.d;
        }
    }


};

int main()
{
    P p;
    std::cout << "p is " << p.report() << std::endl;

    auto x = P("hello");
    std::cout << "x is " << x.report() << std::endl;

    auto y = P("goodbye", as_error);
    std::cout << "y is " << y.report() << std::endl;

    auto z = P(4.4);
    std::cout << "z is " << z.report() << std::endl;


    return 0;
}

预期结果:

p is I am a boolean: false
x is I am a string: hello
y is I am an error: goodbye
z is I am a float: 4.400000

答案 1 :(得分:0)

根据问题标题的要求,将char *替换为shared_ptr<char>,可以进行编译(在自定义删除工具的帮助下),但它几乎从不会有用,并且会几乎可以肯定,谷物没有做正确的事情。由于谷物理解标准库类型,如字符串,为什么不直接使用它们?

由于理事会恰当地证明了工会和非POD类型并没有真正混合,你可以将联合转换为具有虚拟成员函数的继承类来区分类型:

struct P {
  enum tag_type {INT, FLOAT, BOOLEAN, STRING, ERROR };
  virtual tag_type get_tag() const = 0;
  // allow subclsas deletion through base class pointer
  virtual ~P() {}
};
struct PInt: public P {
  tag_type get_tag() { return INT; }
  int a;
};
struct PFloat: public P {
  tag_type get_tag() { return FLOAT; }
  float b;
};
struct PBool: public P {
  tag_type get_tag() { return BOOL; }
  bool c;
};
struct PStr: public P {
  tag_type get_tag() { return STRING; }
  std::string d;
};
struct PError: public P {
  tag_type get_tag() { return ERROR; }
};

您最终得到的代码仍然很笨重,但它可以轻松而强大地处理非POD。使用它可以归结为替换看起来像这样的旧代码:

void process(P& x)
  switch (x.tag) {
    case INT:
      // use x._data.a
      ...
  }
}

...代码使用get_tag()虚拟成员获取标记,dynamic_cast访问最终类的属性:

void process(P& x)
{
  switch (x.get_tag()) {
    case P::INT:
      PInt &intx = dynamic_cast<PInt&>(x);
      // ...use intx.a
    // case P::FLOAT, etc.
  }
}

使用此设置,您可以使用CEREAL_REGISTER_TYPE(Pint)将子类声明为谷物。然后,库将使用运行时类型信息正确地将指针序列化为P