std容器中的抽象类

时间:2014-06-09 17:36:39

标签: c++ c++11 virtual-functions

很多时候,当我编程时,我使用多态,因为它自然地模拟了我需要的对象。另一方面,我经常使用标准容器来存储这些对象,我倾向于避免使用指针,因为这要求我释放对象而不是将它们从堆栈中弹出或要求我确定对象将保持不变我使用指针时的堆栈。当然有各种各样的指针容器对象可以为你完成这项任务,但根据我的经验,它们也不理想甚至烦人。那是;如果存在这样一个简单的解决方案,它本来就是用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。这工作正常,如果对象具有不同的大小,编译器可能会应用切片。但是,由于c ++不知道像Java这样的实例,并且据我所知,没有足够的替代存在,在从向量中获取它们作为foo之后,无法访问继承类的属性。

因此可以使用虚函数,但这不允许使用虚函数,因此不允许在向量中使用它们。请参阅Why can't we declare a std::vector<AbstractClass>?

例如,我可能希望能够打印两个子类,简单的功能,对吧?

#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 ++能够切割我的对象并因此在一个向量中存储不同大小的对象,我已经非常激动了。不幸的是,当基类是抽象的时,它不能再这样做了,如下所述:Why can't we declare a std::vector<AbstractClass>?

一般好的解决方案似乎是使用指针。但是这(1)迫使我做内存管理;(2)我需要改变接口并重新编写很多东西。例如,考虑到我首先有一些类接口返回一个std :: vector&lt; foo&gt;,现在它返回一个std :: vector&lt; foo *&gt;,所以我需要检查并更改foo的所有调用;如果我正在写一个图书馆,这很烦人,甚至不可能。

所以基本上,imho,这是一个小功能添加,带有大代码后果

我的问题是w.r.t.编码标准。我怎样才能防止这些烦恼发生?我应该总是使用指针,并进行所有内存管理吗?我是否应该总是假设一个班级可能会在整个过程中变得抽象?

编辑,答案:基于40的两个答案我制作了这个片段:

#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

5 个答案:

答案 0 :(得分:10)

如果编译器支持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;
}

因此,您不必担心内存释放。

答案 1 :(得分:4)

您可以使用原始指针并正确处理内存

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_ptr http://en.cppreference.com/w/cpp/memory/unique_ptr

答案 2 :(得分:2)

我建议使用shared_ptr,即:

vector<shared_ptr<foo> > 

而不是原始指针。这将解决你绝大多数的内存管理问题。

第二个问题仍然存在,因为您需要在某些方面重新设计界面。但是在使用抽象基类时需要指针,因此没有什么可以做的。如果foo是抽象的,你不能只访问foo作为直接引用。如果可以,请设计界面,使其隐藏这些细节。

对不起,这可能不是你要找的答案,但这是我最好的建议。

答案 3 :(得分:1)

您可以包装类的多态关系并使用智能指针:

#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 << "Foo\n"; }
    };

    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 << "Goo\n"; }
    };

    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();
}

答案 4 :(得分:0)

如何围绕foo编写封装foo*并隐式转换为foo&的包装器?

它使用复制语义,该语义调用存储对象上的基础克隆以进行深层复制。这至少不比按价值存储所有内容的原始意图还差。如果最终将所有内容存储为抽象基础的指针,那么它的间接层级与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_w周围定义foo包装,请参见copy-and-swap idiom

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将类似参考的对象存储在容器中。