STD容器中的抽象类
abstract classes in std containers
在编程时,我经常使用多态性,因为它自然地为我需要的对象建模。另一方面,我经常使用标准容器来存储这些对象,并且我倾向于避免使用指针,因为这要么要求我释放对象,而不是将它们从堆栈中弹出,要么要求我在使用指针时确定对象将留在堆栈中。当然,有各种各样的指针容器对象可以帮你完成这个任务,但在我的经验中,它们也不理想,甚至令人讨厌。这是;如果存在这样一个简单的解决方案,它应该是在c++语言中,对吗?;)
我们来举一个经典的例子:
#include <iostream>
#include <vector>
struct foo {};
struct goo : public foo {};
struct moo : public foo {};
int main() {
std::vector<foo> foos;
foos.push_back(moo());
foos.push_back(goo());
foos.push_back(goo());
foos.push_back(moo());
return 0;
}
参见:http://ideone.com/aEVoSi。这工作得很好,如果对象有不同的sizeof,编译器可能会应用切片。然而,由于c++不像Java那样知道instanceof,而且据我所知,也没有合适的替代方法存在,因此在从vector中作为foo获取继承类的属性后,无法访问它们的属性。
因此可以使用虚函数,但这不允许分配foo,因此不允许在vector中使用它们。请参阅为什么不能声明std::vector
例如,我可能希望能够打印两个子类,简单的功能,对吗?
#include <iostream>
#include <vector>
struct foo {
virtual void print() =0;
virtual ~foo() {}
};
struct goo : public foo {
int a;
void print() { std::cout << "goo"; }
};
struct moo : public foo {
int a,b;
void print() { std::cout << "moo"; }
};
int main() {
std::vector<foo> foos;
foos.push_back(moo());
foos.push_back(goo());
foos.push_back(goo());
foos.push_back(moo());
for(foo& f : foos) {
f.print();
}
return 0;
}
来源:http://ideone.com/I4rYn9
这是一个简单的添加,作为一个设计师,我从来没有想过在预见中需要这种行为。c++能够对对象进行切片,从而将不同大小的对象存储在一个向量中,这已经让我兴奋不已了。不幸的是,当基类是抽象类时,它不能再这样做了,如下所述:为什么我们不能声明std::vector
一般的好的解决方案似乎是使用指针。但是这(1)迫使我做内存管理(2)我需要改变接口和重新编码很多东西。例如,考虑一下我最初有一个类接口返回一个std::vector
我的问题是w.r.t.编码标准。我怎样才能避免这些烦恼的发生呢?我应该一直使用指针,并进行所有的内存管理吗?我是否应该总是假设一个类可能在此过程中变得抽象?
编辑,答案:根据402的答案,我做了这个片段:
#include <iostream>
#include <vector>
#include <memory>
struct foo {
virtual void print() =0;
};
struct goo : public foo {
int a;
void print() { std::cout << "goo"; }
};
struct moo : public foo {
int a,b;
void print() { std::cout << "moo"; }
};
typedef std::unique_ptr<foo> foo_ptr;
int main() {
std::vector<std::unique_ptr<foo> > foos;
foos.push_back(foo_ptr(new moo));
foos.push_back(foo_ptr(new goo));
foos.push_back(foo_ptr(new goo));
foos.push_back(foo_ptr(new moo));
for(auto it = foos.begin(); it!=foos.end(); ++it) {
it->get()->print();
}
return 0;
}
来源:http://ideone.com/ym4SY2
如果你的编译器支持c++ 11的特性,一个解决方案是使用std::vector<std::shared_ptr<foo>>
或std::vector<std::unique_ptr<foo>>
代替原始指针,就像下面的例子:
#include <iostream>
#include <memory>
#include <vector>
struct foo {
virtual void print() = 0;
};
struct goo : public foo {
int a;
void print() { std::cout << "goo"; }
};
struct moo : public foo {
int a,b;
void print() { std::cout << "moo"; }
};
auto main() -> int {
std::vector<std::shared_ptr<foo>> v{std::make_shared<goo>(), std::make_shared<moo>()};
for(auto i : v) {
i->print();
std::cout << std::endl;
}
return 0;
}
或与std::vector<std::unique_ptr<foo>>
:
auto main() -> int {
std::vector<std::unique_ptr<foo>> v;
v.push_back(std::move(std::unique_ptr<goo>(new goo)));
v.push_back(std::move(std::unique_ptr<moo>(new moo)));
for(auto it(v.begin()), ite(v.end()); it != ite; ++it) {
(*it)->print();
std::cout << std::endl;
}
return 0;
}
因此,您不必担心内存释放。
您可以使用原始指针并正确处理内存
std::vector< AbstractBase*>
或者您可以使用智能指针,即std::shared_ptr
(通过指针保留对象的共享所有权的智能指针)或std::unique_ptr
(通过指针保留对象的唯一所有权的智能指针,并在unique_ptr
超出作用域时销毁该对象),让库为您做内存管理。最后你会得到像
std::vector< std::shared_ptr<AbstractBase>>
或
std::vector< std::unique_ptr<AbstractBase>>
http://en.cppreference.com/w/cpp/memory/shared_ptrhttp://en.cppreference.com/w/cpp/memory/unique_ptr
我建议使用shared_ptr ie:
vector<shared_ptr<foo> >
而不是原始指针。这将解决绝大多数内存管理问题。
第二个问题仍然存在,因为你需要在某些方面重新设计你的界面。但是在处理抽象基类时需要指针,因此对此您无能为力。如果foo是抽象的,就不能直接访问它。如果可以的话,设计你的界面,使其隐藏这些细节。
对不起,这可能不是你想要的答案,但这是我最好的建议。
您可以包装类的多态关系并使用智能指针:
#include <iostream>
#include <memory>
#include <vector>
class Base
{
protected:
struct Implementation
{
virtual ~Implementation() {}
virtual void print() const = 0;
};
Implementation& self() const { return *m_self; }
protected:
Base(std::shared_ptr<Implementation> self)
: m_self(self)
{}
public:
void print() const { self().print(); }
private:
std::shared_ptr<Implementation> m_self;
};
class Foo : public Base
{
protected:
struct Implementation : Base::Implementation
{
virtual void print() const { std::cout << "Foon"; }
};
Implementation& self() const { return static_cast<Implementation&>(Base::self()); }
public:
Foo() : Base(std::make_shared<Implementation>()) {}
};
class Goo : public Base
{
protected:
struct Implementation : Base::Implementation
{
virtual void print() const { std::cout << "Goon"; }
};
Implementation& self() const { return static_cast<Implementation&>(Base::self()); }
public:
Goo() : Base(std::make_shared<Implementation>()) {}
};
int main() {
std::vector<Base> v = { Foo(), Goo() };
for(const auto& x: v)
x.print();
}
如何在foo
周围编写封装foo*
并隐式转换为foo&
的包装器?
使用copy语义,调用存储对象上的底层克隆来进行深度复制。这至少不比按值存储所有内容的初衷差。如果您最终将所有内容存储为指向抽象基的指针,那么这与unique_ptr
具有相同的间接级别,但是可复制的(而unique_ptr
则不是)。另一方面,这比shared_ptr
的开销要小。
添加clone()
到抽象层次:
struct foo {
virtual void print() const = 0;
virtual ~foo() {};
virtual foo* clone() = 0;
};
struct goo : public foo {
int a;
void print() const { std::cout << "goo" << std::endl; }
foo* clone() { return new goo(*this); }
};
struct moo : public foo {
int a,b;
void print() const { std::cout << "moo" << std::endl; }
foo* clone() { return new moo(*this); }
};
在foo
周围定义foo_w
包装器,参见复制-交换习惯用法。
struct foo_w {
foo_w(foo *f = nullptr) : fp(f) {}
~foo_w() { delete fp; }
foo_w(const foo_w& that) : fp(that.fp->clone()) {}
foo_w(foo_w&& that) : foo_w() { swap(*this, that); }
foo_w& operator=(foo_w rhs) {
swap(*this, rhs);
return *this;
}
friend void swap(foo_w& f, foo_w& s) {
using std::swap;
swap(f.fp, s.fp);
}
operator foo&() { return *fp; }
operator const foo&() const { return *fp; }
foo& get() { return *fp; }
const foo& get() const { return *fp; }
// if we rewrite interface functions here
// calls to get() could be eliminated (see below)
// void print() { fp->print(); };
private:
foo *fp;
};
用法如下:
#include <iostream>
#include <memory>
#include <vector>
// class definitions here...
int main() {
std::vector<foo_w> foos;
foos.emplace_back(new moo);
foos.emplace_back(new goo);
foos.emplace_back(new goo);
foos.emplace_back(new moo);
// variant 1: do it through a getter:
for(auto it = foos.begin(); it!=foos.end(); ++it) {
it->get().print();
// the presence of a proxy is almost hidden
// if we redefine interface in foo_w
// it->print();
}
// variant 2: use it through reference to foo
for(auto it = foos.begin(); it!=foos.end(); ++it) {
foo& fr = *it;
fr.print();
}
// variant 3: looks really nice with range-for
for(foo& fr : foos)
fr.print();
return 0;
}
包装器的行为完全取决于您的需要。也许如果你同意unique_ptr
不可复制,那是一个更好的方法,对我来说这是至关重要的,所以我最终得到了这个。还可以看看std::reference_wrapper
如何在容器中存储类似引用的对象。
- 无法创建抽象类的实例
- 用pybind11包装C++抽象类时出错
- 检查某些类型是否是模板类 std::optional 的实例化
- 如何处理从一个对象传递到另一个在C++中具有公共抽象类的对象的消息
- 有没有办法按值将纯抽象类的所有子类传递给 C++ 中的函数?
- 抽象类错误,请参阅声明" "是抽象的
- 与 std::unique_ptr 和抽象类关联的编译错误
- 尝试在 std::map 中插入抽象类时没有用于调用的匹配函数
- 管理 std 容器中的抽象类
- 为什么不能将std :: async用于接收对抽象类作为参数的函数
- C++ 接受对抽象类的常量引用的构造函数无法初始化 std::map
- 使用“std::function”(如语法)时,使用抽象类作为模板参数
- 使用std :: make_shared抽象类实例化的错误
- 使用 std::vector 时抽象类类型"Shape"的新表达式无效错误
- 抽象类的Std::shared_ptr来实例化派生类
- std::unique_ptr,默认复制构造函数和抽象类
- STD容器中的抽象类
- 将抽象类的方法作为std::function传递
- 抽象类作为std::map键
- 回调(std::function/std::bind)与接口(抽象类)的优缺点