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;
}
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.