1

I'm working off of someone's code. There is a vector of struct P (Segment). I want to have different types of P. Therefore I defined struct P2 : P. But, since Segment takes just P and not P*, I cannot add element of type P2 to the Segment and use dynamic_cast since it will create a new element of type P and copy just i and I lose the name information.

What are my options here? Do I just have to change vector<P> to vector<P*> and adjust the code everywhere it's being used or create my own type Segment vector<P*> and use where I need it?

Also, why it was used vector<P> and not vector<P*> from the beginning since the vector can have hundreds and even thousands of elements?

struct P
{
  int i;
  P(int i2) : i(i2) {}
}

struct P2 : P
{
   string name;
   P2(string name2, int i) : P(i), name(name2) {}
}

struct P3 : P
{
  ...
}

using Segment = vector<P>;
theateist
  • 13,879
  • 17
  • 69
  • 109
  • use `vector` – user7860670 May 24 '23 at 20:05
  • I would go with a vector of pointers `std::vector

    ` you can also go with `std::vector` as @user7860670 suggested since it ensures type safety and you don't have to deal with pointers, _"Also, why it was used in the first place"_...hard to say without more context

    – underloaded_operator May 24 '23 at 20:06
  • @user7860670, how is this helpful? What if I add `P3 : P` later? – theateist May 24 '23 at 20:08
  • *Also, why it was used `vector

    ` and not `vector

    ` from the beginning since the vector can have hundreds and even thousands of elements?* `vector` is at its best when it can contain objects rather than pointers. It reduces overhead from pointer-chasing and improves cache friendliness because all of the data is in a nice, straight line.

    – user4581301 May 24 '23 at 20:09
  • 3
    Well, `vector` is a homogenous container so it can not store items of different types. So if you want to store items of several derived types without using a pointer then they need to be wrapped into `std::variant` – user7860670 May 24 '23 at 20:10
  • With the pointers you'll have to address ownership of the items you insert. You likely already know you cannot simply `P2 example; container.push_back(&example);` without risking `example` going out of scope before `container` is through with the pointer to it. Perhaps you can resolve this with `std::unique_ptr`, but perhaps my fellow antonymous user is correct and your best ally here is `std::variant` or a separate `vector` and probably eschewing inheritance. There isn't enough information in the question to be sure. – user4581301 May 24 '23 at 20:16
  • Side note: Keyword: [Object slicing](https://stackoverflow.com/q/274626/4581301) – user4581301 May 24 '23 at 20:18
  • 2
    Unless you need polymorphism it's hard to see that `vector

    ` would be better than `vector

    `. It brings extra problems and no obvious gains. Why do you think it might be preferable?

    – john May 24 '23 at 20:31
  • @john, I need a container (`vector` in this case) of different types of `P`. How do I achieve this otherwise? – theateist May 24 '23 at 20:49
  • 1
    BTW, if P does not have a virtual destructor I would discourage using derivation and pointers. – alagner May 24 '23 at 20:51
  • 1
    The question in C++ is always _why_ you want a heterogeneous container. Sometimes the right answer is to just have several containers, one for each type, and then work with all of them at once (*e.g.*, with a variadic template). – Davis Herring May 24 '23 at 20:51
  • @theateist I was answering your question of why `vector

    ` was chosen *from the beginning*. Your situation now is different and `vector

    ` is one solution. Some others are described in the answer below.

    – john May 25 '23 at 05:51
  • @user4581301, can you explain `without risking example going out of scope before container is through with the pointer to it`? – theateist May 26 '23 at 03:27
  • @theateist trivial example: https://godbolt.org/z/co6T7s9oG – user4581301 May 26 '23 at 05:45

1 Answers1

2

You can basically use one of the following concepts:

  • Inheritance/Polymorphism: As you suggested, but then please with a std::vector<std::unique_ptr<P>>. Using a raw pointer as in vector<P*> is discouraged in modern C++, as the vector usually owns the objects. Moreover, I would then define a reasonable interface -- other than in your current example with P2.

  • Type-safe unions: For example, a std::variant<P,P2>, which can contain one of the two types. You then further need to define visitors which act specifically on the contained type.

  • Type erasure: define a common interface as a new class and map the classes to it. In the simplest version, it's just a std::function<std::string()> which captures, e.g., the parenthesis-operator of the classes. This can be thought of as inheritance without base classes.

There's probably more approaches, but those are imo the most common ones.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • I decided to go with Type erasure as I don't have to change a lot. I converted `P` to be templated object that have the Model and Concept. – theateist May 26 '23 at 03:32
  • what if the `P` and derived types are only data containers? Let's say `P` has 3D point, `P1` has extended information, such as solutions (ways) to get to that point and therefore has `std::vector solutions`. Using inheritanace that means I need to change everywhere to pointers (smart pointers). Converting `P` to type erasure will safe me a lot of time and effort. But, they don't really have any common interface, no polymorphism. I'll just use type erasure to be able hold different type of `P` in a vector. Maybe it's better if I ask this as a separate post. – theateist May 27 '23 at 19:30
  • One central question asked aleeady in the comments is why you require the two types in a single vector? If there's no clear reason, just use two vectors, or store the solutions elsewhere. Regarding your question: you can force a common interface, e.g. make `P` also have solutions but let the vector be empty. Just one idea. – davidhigh May 27 '23 at 20:08
  • I did add solutions to `P` so I could continue. But, I want to have different types since `solutions` are not part of `P`. Assuming I cannot refactor the code (since it's a lot and not my code) I think this solves what I need. This gives me an option to have different types of `P` that I can still insert into `vector`. I can add something like `as()` function that will return me the actual type. I created a separate post (https://stackoverflow.com/questions/76348972/extending-struct-with-type-erasure-apprach-to-avoid-major-refactoring). – theateist May 28 '23 at 04:26
  • It's still not completeky clear to me what you're doing, as you say you use type erasure and then show a unique_ptr in the other thread. Anyways, you're `as()` then basically is a `dynamic_cast`, and this will work for both inheritance and type erasure. – davidhigh May 28 '23 at 10:44