复制构造由模板 class 实现的抽象 class 对象的向量

copy construct a vector of objects of an abstract class that is implemented by a template class

我有一个抽象 class 说 A 它是由模板实现的 class 说 BB 专门用于矢量类型实现了复制构造函数和复制赋值运算符,但是当我编译时出现错误:static assertion failed: result type must be constructible from value type of input range。在摘要 class 中声明虚拟副本分配没有帮助,因为签名有:A & operator=(const A &); 但在 B 中它是针对类型 B 实现的,因此签名不匹配。

为什么我有这种奇怪的层次结构? 因为我要实现一个json解析库,需要有一个容器可以存储string、number、bigint、boolean和array类型,所以我实现了hierarchy来实现类型擦除,模板类型是为这 5 种类型,并且只需要专门针对字符串和向量类型。

实际层次结构(最小可重现示例):

 #include <iostream>
 #include <string>
 #include <vector>
 #include <memory>
 #include <iostream>

 class BasicJsonType {
 public:
     virtual std::string toString() const = 0;
     virtual void setNull() = 0;
     // copy assignment method
     // virtual BasicJsonType& operator= (const BasicJsonType &value) = 0;
     ~BasicJsonType() = default;
 };

 template<typename T>
 class BasicJsonTypeInterface: public BasicJsonType {
 protected:
     bool empty = true;

 public:
     virtual const T& get() = 0;
     virtual void set(const T&) = 0;
 };

namespace json {
// json::array is defined as
using array = std::vector<BasicJsonType>;
}

template <typename T>
class JsonValue {
    T x;
public:
    virtual std::string toString() {
        return "";
    }

    virtual const T & get() {
        return this->x;
    }

    virtual void set(const T &value) {
        this->x = value;
    }
};

template<>
class JsonValue<json::array>: public BasicJsonTypeInterface<json::array> {
    std::shared_ptr<json::array> array;

public:
    JsonValue() = delete;

    JsonValue(const JsonValue<json::array> &value): JsonValue(*(value.array)) {
        std::cout << "const JsonValue<json::array> &";
    }

    JsonValue(const json::array &array) {
        std::cout << "const json::array &";
        // error
        this->array.reset(new json::array(array));
    }

    JsonValue(JsonValue<json::array> &&value): JsonValue(static_cast<json::array &&> (*(value.array)))
    { std::cout << "const JsonValue<json::array> &"; }

    JsonValue(json::array &&array) {
        this->array.reset(new json::array(std::move(array)));
        this->empty = false;
    }

    virtual void setNull() { }

    virtual const json::array &get() {
        return *(this->array);
    }

    virtual void set(const json::array &value) {
        this->array.reset(new json::array(value));
     }
};

int main() {}

我创建了接口类型,因为我想为所有类型实现 getset 方法,而不考虑类型。

我搜索了这个错误,我发现我缺少 BasicJsonType 的复制函数,就像它提示的 .

这可能存在一些设计缺陷,因为这是我第一次尝试使用 c++ 进行实际使用,我的目标是 c++11

std::vector<BasicJsonType>;

这是一个无用的类型。

BasicJsonType 是一个摘要 class。 Abstract classes 不是值类型。 std::vector 存储值类型。

std::vector 需要常规类型(如果不需要副本,则为半常规类型)。抽象类型不是常规类型。

有很多方法可以拥有多态常规类型,但它们都需要工作或第 3 方库。


一个小问题:

 ~BasicJsonType() = default;

这应该是虚拟的。

...

有多种方法可以解决将常规类型存储在 std::vector 中的问题。

  1. unique_ptr<BasicJsonType> 存储在您的 vector 中。这允许 moving 但不允许 assignment*.

  2. 实现一个 value_ptr(基于独特的 ptr),了解如何在复制时(虚拟地)克隆其内容。

  3. 实现一个 cow_ptr,它在幕后使用共享 ptr 来处理不可变数据,并进行写时复制。

  4. 创建一个基于 std::anyany_with_interface 保证存储的值匹配接口,并提供 operator->* returns那个界面。

  5. 存储std::variant种json具体类型。写一个辅助函数来获取抽象接口(如果你需要的话)。


由于您支持的类型集已关闭(只有这么多 json 类型),#5 可能是最简单的。

class BasicJsonType {
public:
   virtual std::string toString() const = 0;
   virtual void setNull() = 0;
   virtual bool isNull() const = 0;
protected: // no deleting through this interface
   ~BasicJsonType() = default;
};

// if we find this overload, remember to implement
// your own to_json_string for the type in question
template<class T>
std::string to_json_string( T const& ) = delete;
std::string to_json_string( std::string const& s ) { return s; }
std::string to_json_string( double const& d )
{
    std::stringstream ss;
    ss << d;
    return ss.str();
}

template <typename T>
class JsonValue:public BasicJsonType {
public:
  JsonValue() = default;
  JsonValue(JsonValue const&) = default;
  JsonValue(JsonValue &&) = default;
  JsonValue& operator=(JsonValue const&) = default;
  JsonValue& operator=(JsonValue &&) = default;

  JsonValue( T t ):value(std::move(t)) {}

  std::optional<T> value;
  std::string toString() const final {
    if (value)
      return to_json_string(*value);
    else
      return "(null)";
  }
  bool isNull() const final {
    return !static_cast<bool>(value);
  }
  void setNull() final {
    value = std::nullopt;
  }
};
template<class T>
JsonValue(T)->JsonValue<T>;

您为传递给 JsonValue 的每个 T 创建一个免费函数 to_json_string;如果不这样做,则会出现编译时错误。

剩下的棘手部分是制作一个包含依赖于同一变体的类型的向量的变体。

struct json_variant;
using json_array = std::vector<json_variant>;
struct json_variant :
    std::variant< JsonValue<double>, JsonValue<std::string>, JsonValue<json_array> >
{
    using std::variant< JsonValue<double>, JsonValue<std::string>, JsonValue<json_array> >::variant;
    std::variant< JsonValue<double>, JsonValue<std::string>, JsonValue<json_array> > const& base() const { return *this; }
    std::variant< JsonValue<double>, JsonValue<std::string>, JsonValue<json_array> >& base() { return *this; }
};

BasicJsonType const& getInterface( json_variant const& var )
{
    return std::visit( [](auto& elem)->BasicJsonType const& { return elem; }, var.base());
}
BasicJsonType& getInterface( json_variant& var )
{
    return std::visit( [](auto& elem)->BasicJsonType& { return elem; }, var.base());
}

std::string to_json_string( json_array const& arr )
{
    std::stringstream ss;
    ss << "{";
    for (auto&& elem:arr)
    {
        ss << getInterface(elem).toString();
        ss << ",";
    }
    ss << "}";
    return ss.str();
}

和测试代码:

JsonValue<json_array> bob;
bob.value.emplace();
bob.value->push_back( JsonValue(3.14) );
bob.value->push_back( JsonValue(std::string("Hello world!")) );
std::cout << bob.toString();

好了,C++ 中的值语义 Json 数据类型。

Live example.

中,您可以使用boost::anyboost::variant。我上面所做的一切都适用于他们,除了演绎指南(这只是语法糖)。

所有备选计划也都有效;值指针,放弃副本并使用唯一 ptr,牛指针等

如果您不喜欢 boost,您也可以推出自己的任何或变体,或者找一个独立的。


template<class T, class=void>
struct has_clone_method:std::false_type{};
template<class T>
struct has_clone_method<T,
  decltype( void(&T::clone) )
>:std::true_type{};

template<class T,
  typename std::enable_if<!has_clone_method<T>{}, bool>::type = true
>
std::unique_ptr<T> do_clone( T const& t ) {
  return std::make_unique<T>(t);
}
template<class T,
  typename std::enable_if<has_clone_method<T>{}, bool>::type = true
>
std::unique_ptr<T> do_clone( T const& t ) {
  return t.clone();
}

template<class T>
struct value_ptr:std::unique_ptr<T>
{
  using base = std::unique_ptr<T>;
  using base::base;
  using base::operator=;

  value_ptr()=default;
  value_ptr(value_ptr&&)=default;
  value_ptr& operator=(value_ptr&&)=default;

  template<class D,
    typename std::enable_if<std::is_base_of<T, D>::value, bool> = true
  >
  value_ptr( value_ptr<D> const& o ):
    base( o?do_clone(*o):nullptr)
  {}
  template<class D,
    typename std::enable_if<std::is_base_of<T, D>::value, bool> = true
  >
  value_ptr( value_ptr<D>&& o ):
    base( std::move(o) )
  {}

  value_ptr( base b ):base(std::move(b)) {}

  value_ptr(value_ptr const& o):
    base( o?do_clone(*o):nullptr )
  {}

  value_ptr& operator=(value_ptr const& o) {
    if (!o)
    {
      this->reset();
    }
    else if (this != &o) // test only needed for optimization
    {
      auto tmp = do_clone(*o);
      swap( (base&)*this, tmp );
    }
    return *this;
  }
};
template<class T, class...Args>
value_ptr<T> make_value_ptr( Args&&...args ) {
  std::unique_ptr<T> retval( new T(std::forward<Args>(args)...) );
  return std::move(retval);
}


class BasicJsonType {
public:
   virtual std::string toString() const = 0;
   virtual void setNull() = 0;
   virtual bool isNull() const = 0;
   virtual std::unique_ptr<BasicJsonType> clone() const = 0;
   virtual ~BasicJsonType() = default;
};

using Json = value_ptr<BasicJsonType>;

using JsonVector = std::vector<Json>;

// your own to_json_string for the type in question
template<class T>
std::string to_json_string( T const& ) = delete;
std::string to_json_string( std::string const& s ) { return s; }
std::string to_json_string( double const& d )
{
    std::stringstream ss;
    ss << d;
    return ss.str();
}

template <typename T>
class JsonValue:public BasicJsonType {
public:
  JsonValue() = default;
  JsonValue(JsonValue const&) = default;
  JsonValue(JsonValue &&) = default;
  JsonValue& operator=(JsonValue const&) = default;
  JsonValue& operator=(JsonValue &&) = default;

  JsonValue( T t ):value(make_value_ptr<T>(std::move(t))) {}

  value_ptr<T> value;
  std::string toString() const final {
    if (value)
      return to_json_string(*value);
    else
      return "(null)";
  }
  bool isNull() const final {
    return !static_cast<bool>(value);
  }
  void setNull() final {
    value = nullptr;
  }

  std::unique_ptr<BasicJsonType> clone() const final {
    return std::unique_ptr<JsonValue>(new JsonValue(*this));
  }
};

using JsonNumber = JsonValue<double>;
using JsonString = JsonValue<std::string>;
using JsonArray = JsonValue<JsonVector>;

std::string to_json_string( JsonVector const& arr )
{
    std::stringstream ss;
    ss << "{";
    for (auto&& elem:arr)
    {
        if (elem)
        {
            ss << elem->toString();
        }
        ss << ",";
    }
    ss << "}";
    return ss.str();
}

int main() {
    JsonArray arr;
    arr.value = make_value_ptr<JsonVector>();
    arr.value->push_back( make_value_ptr<JsonNumber>( 3.14 ));
    arr.value->push_back( make_value_ptr<JsonString>( "Hello World" ));
    std::cout << arr.toString() << "\n";
}

这里制作value_ptr,一个支持复制的智能指针

它使用 do_clone,如果存在则调用 .clone(),如果不存在则调用它们的复制构造函数。这允许您创建一个 value_ptr<T>,其中 T 是一个值类型,或者一个 value_ptr<T>,其中 T 是一个具有 .clone() 方法的抽象类型。

我将它用于 JsonValue 本身(可空类型)中的低质量“可选”。

一个JsonVector就是一个value_ptr<BasicJsonType>.

的向量

A BasicJsonTypeJsonValue 中实现,它将数据依次存储在 value_ptr<T>.


迭代改进是将多态性移动到内部细节。

有一个 JsonValuevalue_ptr 存储到 JsonBaseJsonStorage<T> class 实现了 JsonBase,并且本身不可为空。

JsonValue 知道它可能是的所有 4 种类型。它提供了尝试获取特定类型值的接口,如果您请求错误的类型则失败。

这减少了间接性,并给出了不存在不同的双精度、字符串、数组类型的 NULL 的结果。

class JsonData {
public:
   virtual std::string toString() const = 0;
   virtual std::unique_ptr<JsonData> clone() const = 0;
   virtual ~JsonData() = default;
};

using JsonPoly = value_ptr<JsonData>;

template<class T>
class JsonStorage:public JsonData {
public:
  T value;
  std::string toString() const final {
    return to_json_string(value);
  }
  JsonStorage( T t ):value(std::move(t)) {}

  JsonStorage() = default;
  JsonStorage( JsonStorage const& )=default;
  JsonStorage( JsonStorage && )=default;
  JsonStorage& operator=( JsonStorage const& )=default;
  JsonStorage& operator=( JsonStorage && )=default;

  std::unique_ptr<JsonData> clone() const final {
    return std::unique_ptr<JsonStorage>(new JsonStorage(*this));
  }
};

struct JsonValue {
    JsonValue() = default;
    JsonValue( JsonValue const& ) = default;
    JsonValue( JsonValue && ) = default;
    JsonValue& operator=( JsonValue const& ) = default;
    JsonValue& operator=( JsonValue && ) = default;

    explicit operator bool() { return static_cast<bool>(data); }

    std::string toString() const {
        if (!data)
            return "(null)";
        else
            return data->toString();
    }
    template<class T>
    T* get() {
      if (!data) return nullptr;
      JsonStorage<T>* pValue = dynamic_cast<JsonStorage<T>*>(data.get());
      if (!pValue) return nullptr;
      return &(pValue->value);
    }
    template<class T>
    T const* get() const {
      if (!data) return nullptr;
      JsonStorage<T> const* pValue = dynamic_cast<JsonStorage<T>*>(data.get());
      if (!pValue) return nullptr;
      return &(pValue->value);
    }

    JsonValue( double d ):
        data( make_value_ptr<JsonStorage<double>>(d))
    {}
    JsonValue( std::string s ):
        data( make_value_ptr<JsonStorage<std::string>>(s))
    {}
    JsonValue( char const* str ):
        data( make_value_ptr<JsonStorage<std::string>>(str))
    {}
    JsonValue( std::initializer_list<JsonValue> );
private:
  value_ptr<JsonData> data;
};

using JsonVector = std::vector<JsonValue>;
std::string to_json_string( JsonVector const& arr )
{
    std::stringstream ss;
    ss << "{";
    for (auto&& elem:arr)
    {
        ss << elem.toString();
        ss << ",";
    }
    ss << "}";
    return ss.str();
}

JsonValue::JsonValue( std::initializer_list<JsonValue> il ):
    data( make_value_ptr<JsonStorage<JsonVector>>( il ))
{}


int main() {
    JsonValue arr = {JsonValue{3.14}, JsonValue{"Hello World"}};
    std::cout << arr.toString() << "\n";
}

Live example.

在这里,给定一个 JsonValue v(不是模板),当且仅当值包含双精度时,您可以询问 v.get<double>() 哪个 returns 是双精度指针。

JsonValue v = 3.14JsonValue str = "hello".

一样有效

添加新类型需要 to_json_string 重载,并且支持的类型是常规类型。

JsonValue 是多态值类型。虚拟的东西是所有实现细节,不暴露给最终用户。我们在内部进行类型擦除。它基本上是一个 std::any,带有一个额外的 toString 方法。