Home > Software engineering >  Vector of Base unique_ptr causes object slicing on emplace_back(new T())
Vector of Base unique_ptr causes object slicing on emplace_back(new T())

Time:09-22

I'm trying to pass a type as a argument to a method that will properly construct and push a object to a vector of unique_ptr, however the created object is always the Base object. This happens when using emplace_back(), the same works fine if I just instantiate the object.

Constructing the object outside the vector works fine, however I'm not sure how to move the pointer to the vector after.

body_parts.hpp

#include <vector>
#include <string>
#include <fmt/core.h>

using namespace std;

namespace uhcr {

class body_part
{
  public:
    string name = "Generic";

    template <class T>
    void add_body_part()
    {
      this->body_parts.emplace_back<T*>(new T());
      fmt::print("{}\n", this->body_parts.back()->name);
    }

  private:
    vector<unique_ptr<body_part>> body_parts;

};

class torso : public body_part
{
  public:
    string name = "Torso";
    
};

}

character.hpp

#include <string>

#include "components/body_parts.hpp"

using namespace std;

namespace uhcr {
  
class character : public body_part
{
  public:
    string name = "Character";
    
};

}

main.cpp

#define FMT_HEADER_ONLY
#include <memory>
#include <fmt/core.h>
#include "src/character.hpp"

using namespace fmt;
using namespace uhcr;

void create_human() {
  character human;
  human.add_body_part<torso>();
}

int main(void) {
  create_human();  
  return 1;
}

The error is at add_body_part(), when running this code it prints "Generic".

CodePudding user response:

You have multiple data members named name in your subclasses. You probably want to assign values to the member in body_part, not declare new members that shadow it.

class body_part
{
  public:
    body_part() = default;
    string name = "Generic";

    template <class T>
    void add_body_part()
    {
      this->body_parts.emplace_back<T*>(new T());
      fmt::print("{}\n", this->body_parts.back()->name);
    }
  protected:
    body_part(std::string name) : name(name) {}

  private:
    vector<unique_ptr<body_part>> body_parts;

};

class torso : public body_part
{
  public:
    torso() : body_part("Torso") {}
    
};

class character : public body_part
{
  public:
    character() : body_part("Character") {}
    
};

CodePudding user response:

There is no slicing in your code. You seem to expect virtual member variables, but there is no such thing. You get expected output when you use virtual methods:

#include <memory>
#include <vector>
#include <iostream>
#include <string>

using namespace std;


class body_part
{
  public:
    virtual std::string getName() { return "Generic";}
    template <class T>
    void add_body_part()
    {
      this->body_parts.emplace_back<T*>(new T());
      std::cout << this->body_parts.back()->getName();
    }
    virtual ~body_part() = default;
  private:
    vector<unique_ptr<body_part>> body_parts;

};

class torso : public body_part
{
  public:
    std::string getName() override { return "Torso"; }
    
};
 
class character : public body_part
{
  public:
    std::string getName() override { return "Character"; }
};


void create_human() {
  character human;
  human.add_body_part<torso>();
}

int main(void) {
  create_human();  
  return 1;
}

Live Demo


This is a much simpler example of the same effect:

#include <iostream>
#include <string>

struct foo {
    std::string name = "Generic";
    void x(foo& f){
        std::cout << f.name;
    }
    virtual ~foo() = default;
};

struct bar : foo {
    std::string name = "Beneric";
};

int main () {
    foo f;
    bar b;
    f.x(b);
}

bar has two name members. In foo when you write std::cout << f.name it refers to foo::name not to bar::name. You can access bar::name also in foo provided the foo is acutally a bar, but that would lead to some backwards design:

#include <iostream>
#include <string>

struct foo {
    std::string name = "Generic";
    void x(foo& f);
    virtual ~foo() = default;
};

struct bar : foo {
    std::string name = "Beneric";
};

void foo::x(foo& f) {
    std::cout << dynamic_cast<bar&>(f).name;
}


int main () {
    foo f;
    bar b;
    f.x(b);
}

In your example the dynamic_cast would be less of an issue, as you just created a T and you know it is a T, but nevertheless you should use virtual methods rather than relying on casts.

  • Related