2
votes

I've been trying to learn more about private inheritance and decided to create a string_t class that inherits from std::basic_string. I know a lot of you will tell me inheriting from STL classes is a bad idea and that it's better to just create global functions that accept references to instances of these classes if I want to extend their functionality. I agree, but like I said earlier, I'm trying to learn how to implement private inheritance.

This is what the class looks like so far:

class string_t :
#if defined(UNICODE) || defined(_UNICODE)
  private std::basic_string<wchar_t>
#else
  private std::basic_string<char>
#endif
{
public:
  string_t() : basic_string<value_type>() {}

  string_t( const basic_string<value_type>& str ) 
    : basic_string<value_type>( str ) {}

  virtual ~string_t() {}

  using std::basic_string<value_type>::operator=; /* Line causing error */

  std::vector<string_t> split( const string_t& delims )
  {
    std::vector<string_t> tokens;

    tokens.push_back( substr( 0, npos ) );
  }
};

I get the following errors:

1>c:\program files\microsoft visual studio 9.0\vc\include\xutility(3133) : error C2243: 'type cast' : conversion from 'const string_t *' to 'const std::basic_string &' exists, but is inaccessible
1>        with
1>        [
1>            _Elem=wchar_t,
1>            _Traits=std::char_traits,
1>            _Ax=std::allocator
1>        ]

1>        c:\program files\microsoft visual studio 9.0\vc\include\xutility(3161) : see reference to function template instantiation 'void std::_Fill(_FwdIt,_FwdIt,const _Ty &)' being compiled
1>        with
1>        [
1>            _Ty=string_t,
1>            _FwdIt=string_t *
1>        ]

1>        c:\program files\microsoft visual studio 9.0\vc\include\vector(1229) : see reference to function template instantiation 'void std::fill(_FwdIt,_FwdIt,const _Ty &)' being compiled
1>        with
1>        [
1>            _Ty=string_t,
1>            _FwdIt=string_t *
1>        ]

1>        c:\program files\microsoft visual studio 9.0\vc\include\vector(1158) : while compiling class template member function 'void std::vector::_Insert_n(std::_Vector_const_iterator,unsigned int,const _Ty &)'
1>        with
1>        [
1>            _Ty=string_t,
1>            _Alloc=std::allocator
1>        ]

1>        c:\work\c++\string_t\string_t.h(658) : see reference to class template instantiation 'std::vector' being compiled
1>        with
1>        [
1>            _Ty=string_t
1>        ]

The line number (658) in the last error points to the opening brace of the split() function definition. I can get rid of the error if I comment out the using std::basic_string<value_type>::operator=; line. As I understand it, the using keyword specifies that the assignment operator is being brought from private to public scope within string_t.

Why am I getting this error and how can I fix it?

Also, my string_t class doesn't contain a single data member of it's own, much less any dynamically allocated members. So if I don't create a destructor for this class doesn't that mean that if someone were to delete an instance of string_t using a base class pointer the base class destructor would be called?

The following code throws an exception when I have a destructor defined for string_t but works when I comment out the destructor when compiled with VS2008.

basic_string<wchar_t> *p = new string_t( L"Test" );
delete p;
3
don't you think you'd learn more about private inheritance by using it for something where you don't have to ignore everyone's advice? ;)jalf
Private inheritance is usually a bad idea. The only reason to ever use it is to have the EBO optimization apply (Empty Base Optimization), and since std::string has data, it does not qualify. All the other "reasons" (like virtual redefinition) have other solutions (in this precise case, public inheritance + composition). Inheritance is a very strong relationship, notably in term of visibility, it's better than friendship, but not by much. And for this problem, you can perfectly add free-functions to extend an interface.Matthieu M.
@Matthieu - the problem with public inheritance in this case is that std::basic_string does not have a virtual destructor; so trying to delete an instance of string_t using a base class pointer would result in a memory leak. Maybe you can answer the second question: if the string_t class doesn't need a destructor and doesn't declare one, does the problem I mentioned with delete still exist?Praetorian
@praetorian: You are correct that if string's destructor is sufficient, there shouldn't be an issue. By the way, your code compiles for me. I also fleshed it out and tested it; everything should be fine.Potatoswatter
@praetorian: updated the answer.Georg Fritzsche

3 Answers

3
votes

Your default constructor should not be explicit. I think explicitness may be the reason it can't convert std::string to string_t as well, but you erased that construtor from your snippet :vP .

This program compiles and runs fine with GCC 4.2:

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class string_t :
#if defined(UNICODE) || defined(_UNICODE)
  private std::basic_string<wchar_t>
#else
  private std::basic_string<char>
#endif
{
public:
  string_t() : basic_string<value_type>() {}

  string_t( const basic_string<value_type>& str )
    : basic_string<value_type>( str ) {}

  virtual ~string_t() {}

  using std::basic_string<value_type>::operator=; /* Line causing error */

  std::vector<string_t> split( const string_t& delims )
  {
    std::vector<string_t> tokens;

    for ( size_t pen = 0, next = 0; next != npos; pen = next + 1 ) {
        next = find_first_of( delims, pen );
        if ( pen != next ) tokens.push_back( substr( pen, next - pen ) );
    }
    return tokens;
  }

  template<class os>
  friend os &operator<<(os &, string_t const&);
};

template< class os_t >
os_t &operator<<( os_t &os, string_t const &str ) {
        return os << static_cast< string >(str);
}

int main( int argc, char ** argv ) {
        vector<string_t> mytoks = string_t( argv[1] ).split( string( "_" ) );

        for ( vector<string_t>::iterator it = mytoks.begin(); it != mytoks.end(); ++ it ) {
                cerr << * it << endl;
        }
        return 0;
}
2
votes

You need to provide a suitable conversion constructor:

 string_t(const std::basic_string<value_type>&);

Otherwise the compiler doesn't know how to construct a string_t from a std::basic_string<> when you're adding elements to the vector of string_ts.

Regarding the update:
A using declaration for operator=() doesn't help if it is private. Why not just implement your own operator=() instead and forward the assignment:

string_t& operator=(const string_t& s) 
{
    basic_string<value_type>::operator=(s); 
    return *this; 
}

With that it builds fine for me.

0
votes

Unfortunately, you've left out enough that it's uncertain how you're getting the error message you've noted (and it doesn't help that we don't know what was line 657 when you compiled it...).

The probably currently appears to be that substr() is returning an std::string, and the compiler isn't sure how to convert that to a string_t (which is what you're asking it to store in the vector). You probably need/want to add a ctor to handle this, something like:

string_t(std::string const &init) : std::string(init) {}

That lets the compiler know how to convert the string returned by substr() into a string_t that it can push into the vector as you requested.