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

John Gilmore gnu@toad.com
Tue Jul 28 19:46:00 GMT 2009


Hi folks,

I'm implementing my first useful C++ program, and learning just how
grotty it is.  This is with gcc-4.3.3-5ubuntu4 on Ubuntu Jaunty on x86.

I found that memory allocation was opaque to me when using C++ and
libstdc++.  Eventually I stumbled on -Weffc++ which helped a lot.
Then I discovered making operator= and the copy-constructor private,
which meant the compiler would tell me anytime a class got copied
behind my back.

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!

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 -- 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
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