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

Axe*_*ega 6 c++ inheritance stl vector c++11

我一直在考虑从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);
Run Code Online (Sandbox Code Playgroud)

这两个函数的行为应根据逻辑类型vec_a_t或不同而有所不同vec_b_t

这种情况将正常工作,直到有人改变vec_a_t

typedef std::vector<float> vec_a_t;
Run Code Online (Sandbox Code Playgroud)

现在打电话func()突然变得含糊不清.一个现实的例子func()

std::ostream& operator<<(std::ostream& ost, const vec_a_t& v);
Run Code Online (Sandbox Code Playgroud)

现在,如果我们改为继承

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

std::ostream& operator<<(std::ostream& ost, const Vector& v);
Run Code Online (Sandbox Code Playgroud)

也可以宣布

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

std::ostream& operator<<(std::ostream& ost, const Vector2& v);
Run Code Online (Sandbox Code Playgroud)

这显然是消除歧视.

但是,因为std::vector没有从它们派生的虚拟析构函数,所以这是错误的并且可能导致问题.

相反,我们尝试

class Vector : private std::vector<int>
{
public:
   using::size;
   //Add more using declarations as needed.
};
Run Code Online (Sandbox Code Playgroud)

在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.
};
Run Code Online (Sandbox Code Playgroud)

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

  1. 它是一种不同的类型,如果其基础类型发生更改,则不会导致模糊调用.
  2. 它可以向前宣布.
  3. 无需为内部成员类型重新实现转发方法.
  4. 它将表现为真正的STL容器(如果相关/所有方法都暴露在外using).
  5. 它的方法可以被覆盖,例如跟踪呼叫 push_back

基于前面的讨论,我的问题是:

  • 你是否认为在C++ 11中导出这样的STL容器有什么问题?

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

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

编辑:

我知道标准答案是"使用私人领域".我想知道C++ 11中提出的解决方案的实际缺点是什么?私有领域的缺点是必须重新实现一系列方法,这些方法只是转发到底层类型.

这种方法也不是一种选择.

class Vector
{
private:
   std::vector<int> m_type
public:
   std::vector<int>& get_type(){return m_type;}
};
Run Code Online (Sandbox Code Playgroud)

编辑:

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

Yak*_*ont 5

struct BobsContainer {
  typedef std::vector<int> data_type;
  data_type data;
};
Run Code Online (Sandbox Code Playgroud)

我们现在有一个打字std::vector<int>.是的,访问它需要打字.data.,但作为交换,我们摆脱了很多样板和讨厌的行为.

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

{ {blah, blah, blah} }
Run Code Online (Sandbox Code Playgroud)

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

{ std::vector<int>( 3 ) }
Run Code Online (Sandbox Code Playgroud)

如果我们想要避免它们,可以使用它们.

隐藏自己是一个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)... ); }
Run Code Online (Sandbox Code Playgroud)

这是一个比标准更多的样板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)... ); }
Run Code Online (Sandbox Code Playgroud)

如果你想变得非常愚蠢,包含&&&重载(标准容器还没有使用它们).

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

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


M.M*_*M.M 3

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

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

template<typename T> void func(const std::vector<T> &v)
Run Code Online (Sandbox Code Playgroud)

int然后,当该函数需要与 for不同的功能时,它可以在内部调用重载函数float


typedef std::vector<int> coll_t;

您提出的解决方案与您试图避免的问题完全相同!想必您还需要版本coll_a_tcoll_b_t,导致VectorA和 ,VectorB以便您可以利用重载func

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