3

I have a c++ class Foo with an stl std::string as member. Further I have a STL std::vector<Foo> vecFoo containing objects of class Foo by value (not pointers to objects of Foo). Now I was advised by a friend not to implement it in this way but to build the vector with pointers: std::vector<Foo*> or use boost smart pointers. He discussed that a vector, which involves lots of copy operations on its members (allocating more space when adding members etc.) will make problems when the contained classes have dynamic members like std::string. Might there occur any problems?

For my understanding the std::string actually does a deep copy (or copy on write) when class Foo might be copied by the std::vector, as Foo calls copy constructors for all of its members. My friend argued that it is a problem if the string members are of different length among all Foo objects in the vector when the vector is allocating new space. What do you think?

The only reason for using pointers to Foo inside the vector is speed. A pointer to Foo (Foo*) is copied much faster than the complete class Foo, isnt it? Thx for discussion!

Manu343726
  • 13,969
  • 4
  • 40
  • 75
Michbeckable
  • 1,851
  • 1
  • 28
  • 41
  • 3
    afaik, the std::vector stores its 'values' on the heap, and thus it would make no sense to me to use pointers – Zaiborg Oct 31 '13 at 10:46
  • 3
    This question is a bit too open to discussion, and will probably be closed. However, I would advise to stick with your current approach until you **prove** it is a real problem (and move semantics mean it may never be). – BoBTFish Oct 31 '13 at 10:47
  • 3
    Do you have C++11? If so, give your class `nothrow` move copy constructor and move assignment operator. This will make all the internal moving cheap. But in general, profile first and see. – juanchopanza Oct 31 '13 at 10:47
  • 2
    @juanchopanza Or even better, don't, and let the compiler do it. – BoBTFish Oct 31 '13 at 10:48
  • 1
    @BoBTFish Agreed, but that is assuming the type has not disabled the default move copy and assignment special functions. – juanchopanza Oct 31 '13 at 10:50
  • and your friend programs in c or c++? – BЈовић Oct 31 '13 at 10:50
  • 1
    @Zaiborg std::vector stores its values contiguously as it has to be convertible to a straight C array. Thus inserting values or growing the vector causes copies. – RobH Oct 31 '13 at 10:51
  • @BЈовић: From the sound of it, pre-2011 C++. But even then, excessive copying could usually be avoided with a bit of care. – Mike Seymour Oct 31 '13 at 10:52

3 Answers3

2

There is a trade-off here. Is your Foo complicated enough that copying it is significantly more expensive than copying a pointer or a smart pointer? Do you expect the vector to be resized frequently during execution, causing copies? Or, is the cost of copying the vector of Foo acceptable versus the additional complexity of using pointers (and having to clean up afterwards) or smart pointers? Storing pointers in the vector has its own cost, as the objects pointed to won't necessarily be contiguous in memory. (The strings contained within Foos when storing by value will have data scattered in memory though.)

Try the simple implementation (vector of Foo), measure its performance, and only then consider the optimisation of switching to (smart) pointers.

RobH
  • 3,199
  • 1
  • 22
  • 27
  • Thx, you are right with the trade-off. My class is not that complicated, it only has some plain data members float, int etc. Also the vector will never be hold more than 20-30 items. So I go with the by Value implementation. – Michbeckable Oct 31 '13 at 11:20
2

I guess, std::vector with pointers is not going to be more efficient at all if you are going to allocate every element-object with new. But if your element-object is large enough (not becasue of string lengths, but because of its 'non-pointer' members) vector with pointers can perform better or not - it depends on vector reallocation frequency.

Consider the fact that std::vector allocates space logarithmical rarely, becasue it always allocates two times more space than it have allocated previous time.

Things are even better with C++11, where std::vector will use move semantics to avoid for sure copying characters of strings. But you may need to implenent move constructor in your class for this thing to work, if your compiler does not generate default move constructor.

Even before C++11 some std::string implementations used copy-on-write strategy, so just making 'deep copy' of string did not require copying underlying character array unless one of the copies was modified.

it is a problem if the string members are of different length among all Foo objects in the vector

And this is surely non-issue - std::string object, of course, internally holds a pointer to character array, not the array itself.

I am sure, you should analyse your program with profiler before making optimization decisions at such level.

Community
  • 1
  • 1
mas.morozov
  • 2,666
  • 1
  • 22
  • 22
  • I'm not sure "most `std::string` implementations" use COW. In any case, that has been removed in C++11. – juanchopanza Oct 31 '13 at 11:05
  • Thx for answer. What tools can be used to measure performance in visual studio 2012? Has it some integrated? – Michbeckable Oct 31 '13 at 11:18
  • @Michbeckable MS VS 2012 has [built in profiler](http://msdn.microsoft.com/en-us/library/ms182372(v=vs.110).aspx) in its Professional, Premium and Ultimate editions. See [here](http://stackoverflow.com/questions/3096758) how to profile when using other VS editions. – mas.morozov Oct 31 '13 at 11:33
0

You have two choice:

  1. Use std::unique_ptr or std::shared_ptr
  2. Use it by value and provide move semantic

If you use the second choice You have to define Foo in this way

struct Foo
{
    // default ctor
    Foo( const std::string& s = "" ) : name( s ) {}

    // copy ctor
    Foo( const Foo& rhs ) : name( rhs.name ) {}

    // move ctor
    Foo( Foo&& rhs ) : name( std::move( rhs.name ) ) {}

    // assign operator
    Foo& operator = ( const Foo& f )
    {
        name = f.name;
        return *this;
    }

    // move operator
    Foo& operator = ( Foo&& f )
    {
        std::swap( name, f.name );
        return *this;
    }

    string name;
};

FooVec v;

// construct in place
v.emplace_back( "foo1" );
v.emplace_back( "foo2" );

// or move
v.push_back( std::move( Foo( "foo3" ) );
Elvis Dukaj
  • 7,142
  • 12
  • 43
  • 85