Restricting copy-constructor

Hello,

I want a non-accessible (private) copy-constructor of a class except (!) for std::vector of that class, so I can use the vector to store my objects.

Is there a way to do this without writing your own implementation of vector?

Thanks!
Last edited on
If you're using C++11, you can forbid (make private) the copy constructor and instead provide a move constructor. You can then add items to your vector with emplace_back rather than push_back to avoid the copy.

Or....

You can forbid copy-constructing and move-constructing and have a vector of pointers (ie: vector<Foo*> or better yet vector<unique_ptr<Foo>>) instead of a vector of objects.
Thanks for your answer ... I assume there is no "simple" solution using "friend"?
I assume there is no "simple" solution using "friend"?


I guess you could do that, but you really shouldn't.

May I ask.... why are you bothering to restrict copying if the object is copyable? Typically you'd only want to do this if copying is impossible or expensive.
Last edited on
Well my over-all design is quite bad and I really need a way to hack in some safety.

It need to update pointers if the objects of the class in question are copied around in/by a vector storing the objects, but I need to prevent updating these pointers when copying them for some other reason.
So my hack is to prevent copying them at all (except the vector) and make it somewhat safe to use.

I know thats really ugly but it needs to be done.

EDIT: Well copying is -- in a way -- impossible because other objects depend on a pointer to that object. But I still want to store them somehow in a collection (vector).
Last edited on
I assume there is no "simple" solution using "friend"?

Making class std::vector<YourClass> (or rather std::vector<YourClass, std::allocator<YourClass>>) a friend of your YourClass won't help as std::vector<> does not itself create instances the classes it contains. It gets std::allocator<> (or whatever allocator that particular vector is using) to do the work for it.

And the implementors of std::allocator are free to use other (implementation specific) classes or functions to do the real work.

For Visual C++ 2010 I had to add the following two friends to make std::vector<>::push_back() work with class Foo.

1
2
    friend class std::allocator<Foo>;
    friend void std::_Construct<Foo,const Foo&>(Foo*, const Foo&);


and to get std::sort() to work (having added operator< to Foo) I needed to add the following extra friends.

1
2
3
4
    friend void std::swap<>(Foo& _Left, Foo& _Right);
    friend void std::_Insertion_sort1<>(Foo*, Foo*, Foo*);
    friend void std::_Make_heap<>(Foo*, Foo*, std::iterator_traits<Foo*>::difference_type*, Foo*);
    friend void std::_Pop_heap_0<>(Foo*, Foo*, Foo*);


Everything with the underscore prefix is implementation specific. And I only tried push_back, pop_back, operator[], and sorting, so there are prob other friend to make if you want to use other bits of vector-related functionality.

When I fed this code to GCC, as expected, it choked. So badly I couldn't be bothered to try and fix it.

Andy
Last edited on
@elfeck:

I'm not sure I'm understanding your situation.

The bottom line is that an object is either safe to copy or it's not. If it's not safe to copy, then making a special case for vector is no good because when vector does the copy those copies will be unsafe/broken. There isn't really any way to tell whether or not the copy is coming from vector or from elsewhere in the code.

So really, what you're trying to do doesn't really make sense.

Well copying is -- in a way -- impossible because other objects depend on a pointer to that object. But I still want to store them somehow in a collection (vector).


Yes... if other objects are depending on a pointer to this object, then putting them in a vector is a bad way to go, because the vector will need to copy/move them around, thus making any outside pointers invalid.

In this case, you should do my 2nd proposed solution above... and put the pointer in the vector, rather than the object:

1
2
3
4
5
6
7
8
9
10
11
// get rid of this:
vector<Foo> v;
v.push_back( Foo(1,2,3) );

Foo* outsideptr = &v[0]; // <- no good, ptr may become bad when vector is resized

// replace with this:
vector<unique_ptr<Foo>> v;
v.emplace_back( new Foo(1,2,3) );

Foo* outsideptr = v[0].get(); // <- OK, pointer will remain valid for as long as this object is in the vector 


This way... your object will never move in memory. The vector will only copy/move the pointer, and not the object itself.

Using unique_ptr means you don't have to worry about deleting the new'd object.
Last edited on
> It need to update pointers if the objects of the class in question
> are copied around in/by a vector storing the objects,
> but I need to prevent updating these pointers when copying them for some other reason.
> So my hack is to prevent copying them at all (except the vector) and make it somewhat safe to use.

This is one of the typical use-cases where we would opt for std::list<> instead of std::vector<>.
Insertion, removal and moving the elements within the list or across several (allocator-compatible) lists does not invalidate existing iterators, references or pointers.
These remain valid till the element referred/pointed to is deleted.
There is no requirement of updating a pointer to an object if all we are doing are list operations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <string>
#include <list>
#include <algorithm>
#include <memory>
#include <iostream>

struct A
{
    std::string id ;
};

bool operator == ( const A& a, const std::string& str ) { return a.id == str ; }
bool operator < ( const A& first, const A& second ) { return first.id < second.id ; }

int main()
{
    std::list<A> seq { {"one"}, {"two"}, {"three"}, {"four"}, {"five"} } ;

    auto iter= std::find( std::begin(seq), std::end(seq), "three" ) ;
    A& alias = *iter ;
    A* ptr = std::addressof(alias) ;

    const auto dump = [&]
    {
        for( const auto& s : seq ) std::cout << s.id << ' ' ;
        std::cout << '\n' ;
        std::cout << iter->id << '/' << alias.id << '/' << ptr->id
                   << " at address " << ptr << "\n\n" ;
    };

    dump() ;

    seq.sort() ;
    dump() ;

    seq.insert( iter, { {"six"}, {"seven"}, {"eight"} } ) ;
    dump() ;

    seq.erase( std::find( std::begin(seq), std::end(seq), "four" ),
               std::find( std::begin(seq), std::end(seq), "seven" ) ) ;
    dump() ;

    seq.reverse() ;
    dump() ;

    // iterator/reference/pointer to A{"three"} have remained vaild till now
    // everything we have done upto here is a list operation
    // a list does not move its elements around in memory

    std::cout << "--------------------------\n" ;

    std::reverse(  std::begin(seq), std::end(seq) ) ;
    // iterator/reference/pointer to A{"three"} are now invalidated
    // iter no longer 'points' to A{"three"} etc.
    // the algorithm does move things around in memory
    dump() ;
}

http://ideone.com/HRzkXO
Topic archived. No new replies allowed.