Vector containing polymorphic objects: static assertion error

时间:2017-12-18 05:40:39

标签: c++ vector static-assert

After not using C++ since college, I'm trying to use a vector with 2 types of child objects, and I've obviously gotten something wrong.

Initially I used a vector of pointers, which worked, but if I understand correctly, that will leak memory when cleared.

The error I'm getting led me to believe that it had something to do with a static counter in the class (destroyed with last member?), but removing that has not solved it.

Error leads here, in stl_construct.h:

#if __cplusplus >= 201103L
      // A deleted destructor is trivial, this ensures we reject such types:
      static_assert(is_destructible<_Value_type>::value,
            "value type is destructible");
#endif

Well, OK, but my destructors are all explicitly declared. I remembered the parent class should use a virtual destructor and fixed that, but the problem remains all the same.

Moving the constructor/destructor to public on the virtual parent class shouldn't (and indeed didn't) change things.

Right now I'm assuming that I'm misusing the vector somehow. Here's my example:

main:

#include <stdio.h>
#include "Foo.h"
#include <iostream>
Bar buildBar();

int main(int argc, char **argv)
{
    std::vector<Foo> Foos;
    Foos.reserve(1);
    Foos.push_back(buildBar());
    for (int idx=(0);sizeof(Foos);idx++)
    {
        try {
            std::cout <<  "x = " << Foos.at(idx).getLoc().at(0);
        }
        catch (std::exception& e) {
                std::cout <<  idx << ": Index out of range" << std::endl;
            }
    }
Foos.clear();
return 0;
}

Bar buildBar()
{
    Bar* temp = new Bar(5,6);
    return *temp;
}

Foo.h, with the constructor moved to header:

#ifndef FOO_H
#define FOO_H
#include <vector>
class Foo
{
public:
    //int maxoID(){return lastoID;} //0-indexed
    /* Other things */
    virtual std::vector<int> getLoc(){ return {x_Base,y_Base};}
    Foo(): 
    //oID(-1), 
    x_Base(-1),
    y_Base(-1){}
    virtual ~Foo(){}
protected:
    int x_Base;
    int y_Base;
};

class Bar : public Foo
{
public:
    Bar(int x1, int y1):
        x_End(-1),
        y_End(-1)
        { 
            x_Base = x1; 
            y_Base = y1;
        }
    ~Bar(){}
    std::vector<int> getLoc() {return {x_Base,y_Base,x_End,y_End};}
protected:
    int x_End;
    int y_End;

};

#endif // FOO_H

2 个答案:

答案 0 :(得分:1)

Firstly, good on your for questioning your use of raw pointers! Too often people just blindly use them, and it will eventually lead to other problems.

Your current problem is that you have object slicing. When you insert a Bar into a vector<Foo> you lose critical information about Bar. The same happens if you call a function which takes only a Foo, not a Foo& or Foo*.

Depending on your use, you can either use std::unique_ptr, std::shared_ptr, or std::reference_wrapper

Note that you can use raw pointers, and they won't just automatically leak memory, but the vector will not be responsible for the memory (which is the beauty of using one of the smart pointers I pointed out)

This is completely acceptable:

int main()
{
    Foo* f = new Foo;
    {
        std::vector<Foo*> v;
        v.push_back(f);
    } // v goes out of scope and is cleaned up
    delete f; // the vector won't have cleaned this up, it's our responsibility    
}

Using unique_ptr instead just makes this a whole lot less complicated. Of course in such a light example raw pointer is easy to use, but in a larger program it can get out of control:

Note also, and as @juanchopanza points out in the comments, your buildBar function leaks memory. When you call

return *temp;

You create a copy and you'll lose the memory address of temp and therefore be unable to delete it.

Bar buildBar()
{
    Bar* temp = new Bar(5,6);
    return *temp;
}

Bar b = buildBar();
Bar* p = &b; // this only reference the copy, b
delete p; // this is bad, this will (double) delete the memory from b, which is not the same as temp.

b will be cleaned up automatically when it goes out of scope (which is bad if you've also tried to delete it), but you've no way to delete the temp

答案 1 :(得分:0)

Welcome back to C++!

You can't put derived types in a vector of base types. The instances can be of base type only. If you try to add a derived type to this vector, it will only copy the base parts of the derived class.

You were right the first time--You need a vector of base pointers. This will not leak as long as you delete the pointers when removing them from the vector.

However, this is 2017 and we now like to avoid new/deletes in general. So you could use a vector of unique_ptr (C++'11), which auto-delete the memory when they go out of scope.

std::vector<std::unique_ptr<Bar>> v;
v.push_back(std::make_unique<Bar>(0, 0));

You could also use a vector of variants (C++'17), which are the newfangled typesafe union.

typedef std::variant<Bar, Foo> MyBar
std::vector<MyBar> v;

use std::visit or std::get on an element to get its type back.

EDIT: Here is some code to extract the elements from a vector of variants

bool apply(int& i)
{
    std::cout << "integer: " << i << std::endl;
    return true;
}

bool apply(double& d)
{
    std::cout << "double: " << d << std::endl;
    return true;
}

void test()
{
    typedef std::variant<int, double> asdf;

    std::vector<asdf> v;

    v.push_back(10);
    v.push_back(0.5);

    auto myLambda = [](auto&& arg) { return apply(arg); };  // use of auto (the 2nd one) is required.

    for (auto& elem : v)
    {
        std::visit(myLambda, elem);
    }
}