Can I have polymorphic containers with value semantics in C++?

后端 未结 9 1355
半阙折子戏
半阙折子戏 2020-12-02 18:35

As a general rule, I prefer using value rather than pointer semantics in C++ (ie using vector instead of vector). Usuall

相关标签:
9条回答
  • 2020-12-02 19:02

    Just to add one thing to all 1800 INFORMATION already said.

    You might want to take a look at "More Effective C++" by Scott Mayers "Item 3: Never treat arrays polymorphically" in order to better understand this issue.

    0 讨论(0)
  • 2020-12-02 19:05

    You might also consider boost::any. I've used it for heterogeneous containers. When reading the value back, you need to perform an any_cast. It will throw a bad_any_cast if it fails. If that happens, you can catch and move on to the next type.

    I believe it will throw a bad_any_cast if you try to any_cast a derived class to its base. I tried it:

      // But you sort of can do it with boost::any.
    
      vector<any> valueVec;
    
      valueVec.push_back(any(Parent()));
      valueVec.push_back(any(Child()));        // remains a Child, wrapped in an Any.
    
      Parent p = any_cast<Parent>(valueVec[0]);
      Child c = any_cast<Child>(valueVec[1]);
      p.write();
      c.write();
    
      // Output:
      //
      // Parent: 1
      // Child: 2, 2
    
      // Now try casting the child as a parent.
      try {
          Parent p2 = any_cast<Parent>(valueVec[1]);
          p2.write();
      }
      catch (const boost::bad_any_cast &e)
      {
          cout << e.what() << endl;
      }
    
      // Output:
      // boost::bad_any_cast: failed conversion using boost::any_cast
    

    All that being said, I would also go the shared_ptr route first! Just thought this might be of some interest.

    0 讨论(0)
  • 2020-12-02 19:07

    Most container types want to abstract the particular storage strategy, be it linked list, vector, tree-based or what have you. For this reason, you're going to have trouble with both possessing and consuming the aforementioned cake (i.e., the cake is lie (NB: someone had to make this joke)).

    So what to do? Well there are a few cute options, but most will reduce to variants on one of a few themes or combinations of them: picking or inventing a suitable smart pointer, playing with templates or template templates in some clever way, using a common interface for containees that provides a hook for implementing per-containee double-dispatch.

    There's basic tension between your two stated goals, so you should decide what you want, then try to design something that gets you basically what you want. It is possible to do some nice and unexpected tricks to get pointers to look like values with clever enough reference counting and clever enough implementations of a factory. The basic idea is to use reference counting and copy-on-demand and constness and (for the factor) a combination of the preprocessor, templates, and C++'s static initialization rules to get something that is as smart as possible about automating pointer conversions.

    I have, in the past, spent some time trying to envision how to use Virtual Proxy / Envelope-Letter / that cute trick with reference counted pointers to accomplish something like a basis for value semantic programming in C++.

    And I think it could be done, but you'd have to provide a fairly closed, C#-managed-code-like world within C++ (though one from which you could break through to underlying C++ when needed). So I have a lot of sympathy for your line of thought.

    0 讨论(0)
  • 2020-12-02 19:17

    Since the objects of different classes will have different sizes, you would end up running into the slicing problem if you store them as values.

    One reasonable solution is to store container safe smart pointers. I normally use boost::shared_ptr which is safe to store in a container. Note that std::auto_ptr is not.

    vector<shared_ptr<Parent>> vec;
    vec.push_back(shared_ptr<Parent>(new Child()));
    

    shared_ptr uses reference counting so it will not delete the underlying instance until all references are removed.

    0 讨论(0)
  • 2020-12-02 19:18

    I just wanted to point out that vector<Foo> is usually more efficient than vector<Foo*>. In a vector<Foo>, all the Foos will be adjacent to each other in memory. Assuming a cold TLB and cache, the first read will add the page to the TLB and pull a chunk of the vector into the L# caches; subsequent reads will use the warm cache and loaded TLB, with occasional cache misses and less frequent TLB faults.

    Contrast this with a vector<Foo*>: As you fill the vector, you obtain Foo*'s from your memory allocator. Assuming your allocator is not extremely smart, (tcmalloc?) or you fill the vector slowly over time, the location of each Foo is likely to be far apart from the other Foos: maybe just by hundreds of bytes, maybe megabytes apart.

    In the worst case, as you scan through a vector<Foo*> and dereferencing each pointer you will incur a TLB fault and cache miss -- this will end up being a lot slower than if you had a vector<Foo>. (Well, in the really worst case, each Foo has been paged out to disk, and every read incurs a disk seek() and read() to move the page back into RAM.)

    So, keep on using vector<Foo> whenever appropriate. :-)

    0 讨论(0)
  • 2020-12-02 19:19

    I'm using my own templated collection class with exposed value type semantics, but internally it stores pointers. It's using a custom iterator class that when dereferenced gets a value reference instead of a pointer. Copying the collection makes deep item copies, instead of duplicated pointers, and this is where most overhead lies (a really minor issue, considered what I get instead).

    That's an idea that could suit your needs.

    0 讨论(0)
提交回复
热议问题