Can't create list members if base type doesn't copy-construct?

Federico Terraneo fede.tft@hotmail.it
Tue Jul 28 19:49:00 GMT 2009


Well, I'm not a libstdc++ developer, but this question is easy to answer 
  anyway since it is only related to the language.

John Gilmore wrote:
> I had just about eliminated all the copying from my code.  Then I
> discovered that when my class had no public copy-constructor, I
> couldn't find any way to make an STL list out of it!
> 
> It's opaque (i.e. undocumented) that when you make a list of type X,
> you can't just feed it things of type X and get them added to the list
> without COPYING them.  This copying has nasty effects if you have any
> dynamically allocated stuff pointed to inside X.  Even if you go to
> all the trouble to make new dynamically allocated subcopies in the
> copy-constructor, the list container (and possibly all containers?)
> stupidly force you to *make* a deeply nested copy -- and then delete
> the original deeply nested object!

That's what the C++ standard says, when you declare a container of type 
T, elements of type T are stored by copy. For many classes, this is the 
desired behavior. In many cases there's little or no performance penalty 
in doing that. Also consider that declaring copy constructor and 
operator= private in your classes for no reason is a bad programming 
practice that will make your classes harder to use.
If your class is heavy to copy construct, you can always create a 
container of pointers to type T, like this:

class Foo { ... };

std::list<Foo *> l;

l.push_front(new Foo);

Of course, if you choose to do the memory management yourself (and I 
repeat, for most classes is not necessary), you have to remember to 
delete elements from the list at the end, or you'll cause a memory leak

std::list<Foo *>::iterator it;
for(it=l.begin();it!=l.end();it++) delete *it;
l.clear();

> 
> As I thought about it harder (and started reading the code for the
> implementation), I figured out that the list next and prev pointers
> ended up getting allocated in the same block of memory as all of Type
> X's instance variables.  If you make something of Type X that doesn't
> have those next and prev pointers, you can't just hook it into the
> list.  So I thought, well, I'll be smarter.  I won't COPY my class at
> all.  I'll create a default instance of the class, AS A LIST ELEMENT
> (with the next and prev pointers already included), and then start
> setting its instance variables.  Then I can splice it into my big
> list, without doing any copying.  I found a way to create a new little
> list with N default list elements, right there in the documentation:
> 
>   http://msdn.microsoft.com/en-us/library/bb385353.aspx
> 
>   The constructor:
> 
>     explicit list(size_type count);
> 
>   initializes the controlled sequence with count elements each with
>   value value_type(). You use it to fill the container with elements
>   all having the default value.
> 
> (yes, this is a Microsoft manual.  Of course I wasn't reading the &%*@
> proprietary C++ standards document that isn't available online -- and
> of course I wasn't using the nonexistent GNU libstdc++
> user's-as-opposed-to-hacker's manual 

The documentation for libstdc++ is probably targeted at people who wish 
to write patches for the library itself, not at end user.
Since libstdc++ is an implementation of a standard, you will probably 
have more luck consulting a generic C++ stdlib manual, or an online 
reference like this http://www.cplusplus.com/reference/ whose content is 
applicable to any conformant C++ stdlib implemetation (therefore also 
libstdc++)

-- so I was scraping by with
> whatever random C++ standard library documentation exists on the Web.
> Microsoft's happened to be one of the ones I found.)
> 
> So I tried the no-copying strategy.
> 
> It worked great except the one place where it needed to work -- where
> I allocate a list of Type X that contains one default element.  The
> manual says you can do that this way:
> 
>     #include "list"
> 
>     class short_msg { int a; };
> 
>     class short_msg_pending: public short_msg {
>       private:
>         short_msg_pending (const short_msg_pending &smp);
>     };
> 
>     typedef std::list<short_msg_pending> short_msg_p_list;
> 
>     int main() {
>       short_msg_p_list *smpl;
>       smpl = new short_msg_p_list (1);
>     }
> 
> This supposedly makes smpl a pointer to a list, and the list contains
> a single element, which is built by the default constructor for class
> short_msg_pending.  Unfortunately the GNU implementation doesn't do that.
> 
> Instead, it creates a default instance of class short_msg_pending and
> then uses a copy-constructor to make each new element of the list (in
> this case it's only making one).  Since I'd made the copy-constructor
> private, it couldn't do that, so it gave me one of the best 9-line
> inscrutable compiler error messages I've ever seen:
> 
> $ gcc -Weffc++ -g -o foof foof.cpp -lstdc++
> foof.cpp:5: warning: base class â??class short_msgâ?? has a non-virtual destructor
> foof.cpp: In constructor â??std::list<_Tp, _Alloc>::list(size_t, const _Tp&, const _Alloc&) [with _Tp = short_msg_pending, _Alloc = std::allocator<short_msg_pending>]â??:
> foof.cpp:14: error: no matching function for call to â??short_msg_pending::short_msg_pending()â??
> foof.cpp:7: note: candidates are: short_msg_pending::short_msg_pending(const short_msg_pending&)
> foof.cpp: In member function â??void __gnu_cxx::new_allocator<_Tp>::construct(_Tp*, const _Tp&) [with _Tp = short_msg_pending]â??:
> /usr/include/c++/4.3/bits/stl_list.h:463:   instantiated from â??std::_List_node<_Tp>* std::list<_Tp, _Alloc>::_M_create_node(const _Tp&) [with _Tp = short_msg_pending, _Alloc = std::allocator<short_msg_pending>]â??
> /usr/include/c++/4.3/bits/stl_list.h:1341:   instantiated from â??void std::list<_Tp, _Alloc>::_M_insert(std::_List_iterator<_Tp>, const _Tp&) [with _Tp = short_msg_pending, _Alloc = std::allocator<short_msg_pending>]â??
> /usr/include/c++/4.3/bits/stl_list.h:876:   instantiated from â??void std::list<_Tp, _Alloc>::push_back(const _Tp&) [with _Tp = short_msg_pending, _Alloc = std::allocator<short_msg_pending>]â??
> /usr/include/c++/4.3/bits/stl_list.h:1304:   instantiated from â??void std::list<_Tp, _Alloc>::_M_fill_initialize(size_t, const _Tp&) [with _Tp = short_msg_pending, _Alloc = std::allocator<short_msg_pending>]â??
> /usr/include/c++/4.3/bits/stl_list.h:521:   instantiated from â??std::list<_Tp, _Alloc>::list(size_t, const _Tp&, const _Alloc&) [with _Tp = short_msg_pending, _Alloc = std::allocator<short_msg_pending>]â??
> foof.cpp:14:   instantiated from here
> foof.cpp:7: error: â??short_msg_pending::short_msg_pending(const short_msg_pending&)â?? is private
> /usr/include/c++/4.3/ext/new_allocator.h:108: error: within this context
> 
> I think this should in theory be fixable in the std::list

This is common among beginners. In template code, if you use the 
template in a wrong way, the compiler will report that the template is 
broken, while instead the truth is that your code is broken. Errors 
about template are usually a bit difficult to understand, but it just 
takes a little practice. Anyway you've interpreted it correctly: the 
compiler complains about your class having a private copy constructor.

> implementation, by making a separate 1-argument constructor for the
> list (separate from the 3-argument one that's defaulting two of its
> arguments).  Doing this probably requires other adjustment elsewhere.
> Since I'm a C++ newbie I'll refrain from trying to submit a patch for
> a heavily templated library implementation...
> 
> I find it quite odd that lists provide no way to make a new element of
> your type with any of your type's handy constructors (other than the
> default constructor, or a copy-constructor), but that's just one of
> the warts of C++.
> 
> 	John Gilmore
> 
> 
> 



More information about the Libstdc++ mailing list