tags:

views:

587

answers:

4

Is there a way to undefine the += on strings and wstrings for chars and wchar_t?

Basically I want to avoid bugs like the following:

int age = 27;

std::wstring str = std::wstring(L"User's age is: ");
str += age;

std::string str2 = std::string("User's age is: ");
str2 += age;

The above code will add the ascii character 27 to the string instead of the number 27.

I obviously know how to fix this, but my question is: how do I produce a compiler error in this situation?

Note: You can override += on std::string and int to properly format the string, but this is not what I want to do. I want to completely disallow this operator on these operands.

+3  A: 

1) Create a string class of your own that inherits/contains the std::string.

2) In this class overload the operator+=(int val) and make it private.

3) Use this class for all your string operations.

This will make the compiler flag errors whenever you do something like this:

MyString str;
str += 27;
Frederick
hard to enforce this for entire team, and code base is really really big
Brian R. Bondy
I think this is a good solution to the problem the OP has. But it would still get confusing later on to other developers.
nlaq
I think this is the only safe way
Brian R. Bondy
I think too. The time to write my answer and you already said it in 3 sentences :)
Klaim
+7  A: 

You cannot deactivate a specific function of a class (here std::basic_string) as it is it's interface that clearly (and officially) allow that manipulation. Trying to overload the operator will only mess things up.

Now, you can "wrap" std::basic_string in another class, using private inheritance or composition and then use the public interface as a proxy to the std::basic_string part, but only the functions you want to be usable.

I recommand first replacing you string types with typedefs :

namespace myapp
{
    typedef std::string String;
    typedef std::wstring UTFString;
}

Then once your application compile fine after having replaced std::string and std::wstring by myapp::String and myapp::UTFString (those are example names), you define the wrapper class somewhere :

namespace myapp
{
/** std::basic_string with limited and controlled interface.
    */
    template< class _Elem, class _Traits, class _Ax >
    class limited_string 
    {
    public:
     typedef std::basic_string< _Elem , _Traits, _Ax > _String; // this is for easier writing
     typedef limited_string< _Elem, _Traits, _Ax > _MyType; // this is for easier writing

    private:
     _String m_string; // here the real std::basic_string object that will do all the real work!

    public:

     // constructor proxies... (note that those ones are not complete, it should be exactly the same as the original std::basic_string
     // see some STL docs to get the real interface to rewrite)
     limited_string() : m_string {}
     limited_string( const _MyType& l_string ) : m_string( l_string.m_string ) {}
     limited_string( const _Elem* raw_string ) : m_string( raw_string ) {}
     //... etc...

     // operator proxies...
     _MyType& operator= ( const _MyType& l_string ) 
     {
      m_string = l_string.m_string;
     }
     // etc...
     // but we don't want the operator += with int values so we DON'T WRITE IT!

     // other function proxies...
     size_t size() const { return m_string.size(); } // simply forward the call to the real string!
     // etc...you know what i mean...

     // to work automatically with other STL algorithm and functions we add automatic conversion functions:
     operator const _Elem*() const { return m_string.c_str(); } 

     // etc..  


    };
}

...then, you simply replace those lines :

// instead of those lines...
    //typedef std::string String; 
    //typedef std::wstring UTFString;

    // use those ones
    typedef limited_string< char, std::char_traits<char>, std::allocator<char> >    String; // like std::string typedef
    typedef limited_string< wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >  UTFString; // like std::wstring typedef

... and your example will crash :

error C2676: binary '+=' : 'myapp::UTFString' does not define this operator or a conversion to a type acceptable to the predefined operator
error C2676: binary '+=' : 'myapp::String' does not define this operator or a conversion to a type acceptable to the predefined operator

Here is the full test application code i wrote to prove that (compiled on vc9) :

#include <string>
#include <iostream>

namespace myapp
{

    /** std::basic_string with limited and controlled interface.
    */
    template< class _Elem, class _Traits, class _Ax >
    class limited_string 
    {
    public:
     typedef std::basic_string< _Elem , _Traits, _Ax > _String; // this is for easier writing
     typedef limited_string< _Elem, _Traits, _Ax > _MyType; // this is for easier writing

    private:
     _String m_string; // here the real std::basic_string object that will do all the real work!

    public:

     // constructor proxies... (note that those ones are not complete, it should be exactly the same as the original std::basic_string
     // see some STL docs to get the real interface to rewrite)
     limited_string() : m_string {}
     limited_string( const _MyType& l_string ) : m_string( l_string.m_string ) {}
     limited_string( const _Elem* raw_string ) : m_string( raw_string ) {}
     //... etc...

     // operator proxies...
     _MyType& operator= ( const _MyType& l_string ) 
     {
      m_string = l_string.m_string;
     }
     // etc...
     // but we don't want the operator += with int values so we DON'T WRITE IT!

     // other function proxies...
     size_t size() const { return m_string.size(); } // simply forward the call to the real string!
     // etc...you know what i mean...

     // to work automatically with other STL algorithm and functions we add automatic conversion functions:
     operator const _Elem*() const { return m_string.c_str(); } 

     // etc..  


    };

    // instead of those lines...
    //typedef std::string String; 
    //typedef std::wstring UTFString;

    // use those ones
    typedef limited_string< char, std::char_traits<char>, std::allocator<char> >    String; // like std::string typedef
    typedef limited_string< wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >  UTFString; // like std::wstring typedef 
}

int main()
{
    using namespace myapp;

    int age = 27;

    UTFString str = UTFString(L"User's age is: ");
    str += age; // compilation error!
    std::wcout << str << std::endl;

    String str2 = String("User's age is: ");
    str2 += age; // compilation error!
    std::cout << str2 << std::endl;

    std::cin.ignore();

    return 0;
}

I think it would cleanly resolve your problem, but you'll have to wrapp all the functions.

Klaim
thanks for the time spent detailing this solution :)
Brian R. Bondy
No problem :) I wanted to be sure it was feasable so i tried first. Kind of defensive programing mindset :p
Klaim
why not derive private from basic_string, and use using declaration to redeclare functions of basic_string within your class scope? i think i would prefer that approach
Johannes Schaub - litb
Yes I suggested that in the second paragraph, I didn't check but I guess it would be the same.
Klaim
@litb: Your not supposed to use the string class as a base class for deriving other types. This is indicated by not having a virtual destructor.
Martin York
Martin, yeah because of that i would derive private. you can't call delete on it then from outside. but, actually, i have just read an article by herb sutter, and frankly, i wouldn't use private inheritance anymore :D
Johannes Schaub - litb
@Martin York: So what? basic_string does not have virtual methods. There is no polymorphism in the code so everything will be fine.
Sergey Skoblikov
Isn't UTFString a confusing typedef for std::wstring?
JesperE
Sergey, he talked about that clients could call delete on a pointer to std::string, when really it points to our own string object. but anyway deriving private would fix that.
Johannes Schaub - litb
but anyway, read but anyway read http://www.gotw.ca/publications/mill06.htm and http://www.gotw.ca/gotw/060.htm and http://www.gotw.ca/publications/mill07.htm nice articles imho. but the mere fact that it has no virtual destructor can signal that it's not supposed to be derived from i think too.
Johannes Schaub - litb
@JesperE : Feel free to change it to any more explicit name you want, it's only an example.
Klaim
If you want to play rough, you could even, for your project, override the <string> header to contain your limited_string implementation. Saves a lot of wrapping! Fun guaranteed in the maintenance phase though :)
xtofl
It wouldn't be a good idea as the goal here is not to create a new string but only to limit the heavily-tested-and-secure-standard string. :)
Klaim
+5  A: 

Most source control systems allow you to run sanity checks against your code during checkin. So you could set up a test the performs the validation and refuses the checkin on failure:

Example:

Test Script:

#!/bin/tcsh
# Pass the file to test as the first argument.

echo "#include <string>\
void operator+=(std::string const& , int const&);\
void operator+=(std::string const& , int);"\
| cat - $1 \
| g++ -c -x c++ -  >& /dev/null


echo $status

This script fakes the addition of the two operators above (without actually altering the source). This will cause any use of the operator+ with strings and a char to fail even if the original code compiles.

NB: operator+= idea stolen from litb. Who has since deleted his example. But credit where it was due.

Martin York
I think this line of thought is the nicest solution.
Henk
+3  A: 

There is no easy way to prevent it, but there is an easy way to find it. Write a small program that uses this operator, then look at the mangled symbol for the operator+= you want to disallow. This symbol is a unique string. As part of your automated tests, use DUMPBIN (or the equivalent Linux/Mac tool) to check whether this mangled symbol is present in your object files.

MSalters