从C ++ 11中的STL容器继承

时间:2014-07-22 22:20:17

标签: c++ inheritance c++11 vector stl

我一直在考虑从C ++ 11中继承STL容器。我知道如果没有一些考虑因素就不应该这样做,因为没有虚拟析构函数。

根据我的理解,使用typedef是为STL容器命名的首选方法。

然而,typedef本身并非没有问题。首先,它们不能轻易地被前向声明,并且两个typedef可能意外地是相同的类型。

请注意以下几点:

typedef std::vector<int> vec_a_t;
typedef std::vector<float> vec_b_t;

void func(const vec_a_t& v);
void func(const vec_b_t& v);

这两个函数的行为应该有所不同,具体取决于逻辑类型vec_a_tvec_b_t

在有人将vec_a_t更改为

之前,这种情况会正常
typedef std::vector<float> vec_a_t;

现在,对func()的调用突然变得含糊不清。 func()的一个现实例子是

std::ostream& operator<<(std::ostream& ost, const vec_a_t& v);

现在,如果我们继承像

那样
class Vector : public std::vector<int>
{};

std::ostream& operator<<(std::ostream& ost, const Vector& v);

也可以声明

class Vector2 : public std::vector<int> {};

std::ostream& operator<<(std::ostream& ost, const Vector2& v);

这显然是消极的。

然而,由于std::vector没有从它们派生的虚拟析构函数,因此这是错误的并且可能导致问题。

相反,我们尝试

class Vector : private std::vector<int>
{
public:
   using::size;
   //Add more using declarations as needed.
};

在C ++ 11之前,这也存在问题,因为我们必须重新声明构造函数,并且可以继承我们的Vector类。

但是在C ++ 11中可以执行以下操作

class Vector final : private std::vector<int>
{
public:
   using std::vector<value_type>::vector;
   using std::vector<value_type>::size;
   //More using directives as needed.
};

从我所看到的,这解决了很多问题,为什么不应该从STL容器派生出来。它还有以下好处:

  1. 它是一种独特的类型,如果其基础类型发生更改,则不会导致模糊调用。
  2. 可以向前宣布。
  3. 无需为内部成员类型重新实现转发方法。
  4. 它将表现为真正的STL容器(如果使用using公开相关/所有方法)。
  5. 可以覆盖其方法,例如跟踪对push_back
  6. 的来电

    根据之前的讨论,我的问题是:

    • 你认为在这里推导出像这样的STL容器有什么问题吗? C ++ 11

    • 我错过了什么或者这种编码风格会导致任何问题 下线?

    • 如果这种新类型具有自己的状态,是否会导致任何问题 (例如跟踪push_back的来电次数?

    编辑:

    我知道标准答案是&#34;使用私人字段&#34;。我想知道C ++ 11中提出的解决方案的实际缺点是什么?私有领域的缺点是必须重新实现一系列直接转化为基础类型的方法。

    这种做法也不是一种选择。

    class Vector
    {
    private:
       std::vector<int> m_type
    public:
       std::vector<int>& get_type(){return m_type;}
    };
    

    编辑:

    不要在最终解决方案中使用typedef coll_t来避免我的新typedef导致问题的答案,只是为了简化输入。

3 个答案:

答案 0 :(得分:5)

struct BobsContainer {
  typedef std::vector<int> data_type;
  data_type data;
};

我们现在有一个键入的std::vector<int>。是的,访问它需要输入.data.,但作为交换,我们摆脱了很多样板和恶劣的行为。

如果我们想构建底层std::vector,对于隐式构造函数,我们只需:

{ {blah, blah, blah} }

这确实更喜欢通过标准构造函数调用列表初始化,因此:

{ std::vector<int>( 3 ) }
如果我们想避免它们,可以使用

隐藏你是std::vector是相对毫无意义的。如果你的实现是“我是一个秘密std::vector并且我将所有方法重定向到它”,请跳过这个秘密?

你可以通过隐藏一些std::vector<int>而不是其他隐私来强制实施一些不变量:但是如果你走得那么远,那么请使用private解决方案。编写转发方法,特别是在C ++ 1y中,变得异常简单:

template<typename... Args> decltype(auto) insert( Args&&... args ) { return data.insert( std::forward<Args>(args)... ); }

这是比using std::vector<int>::insert;更多的样板,但只是一点点。作为交换,你不再使用'is-a'和继承做出奇怪的事情。

对于同时包含const和非const重叠的方法:

template<typename... Args> decltype(auto) insert( Args&&... args ) const { return data.insert( std::forward<Args>(args)... ); }

如果你想变得非常愚蠢,请加载&&&重载(标准容器尚未使用它们)。

因此,如果您要转发几乎所有内容,只需包含vector。如果您隐藏几乎所有内容,只需包含私有vector并转发。只有在你隐藏了大约一半类并暴露另一半的奇怪的不稳定情况下,using container;private继承才会合理。

当您想在通用代码中利用空基类优化时,通过继承进行组合非常重要。否则通常不是一个好主意。没有设计为继承的标准容器。

答案 1 :(得分:3)

有几种方法可以解决func的重载问题。如果你甚至同意这是一个问题;我不这样做:“如果有人将typedef foo A;更改为typedef bar A;,我的程序可能会停止编译”这不是可以避免的事情。更改typedef的人有责任检查他们正在做什么。

无论如何,解决方案是使用:

template<typename T> void func(const std::vector<T> &v)

然后,当函数需要int的不同功能而不是float时,函数可以在内部调用重载函数。


  

typedef std::vector<int> coll_t;

您提出的解决方案与您要避免的问题完全相同!大概你还需要版本coll_a_tcoll_b_t,导致VectorAVectorB,这样你就可以利用重载的func

但如果有人将此typedef更改为std::vector<float>

答案 2 :(得分:-1)

Typedef完全没问题。如果你不小心为同一类型提供了两个不同的typedef,那么你的代码结构是错误的。

但是,如果您有某些原因无法保证typedef的唯一性,可以尝试以下方法: 使用std :: type_traits,您可以检查两个typedef是否完全相同。如果是,您可以使用std :: enable_if来影响第二个声明。

constexpr bool isSame = std::is_same<vec_a_t, vec_b_t>::value;
void func(const vec_a_t& v);
template <typename = std::enable_if<!isSame>::type> func(const vec_b_t& v);