Elegant way to prevent namespace poisoning in C++

前端 未结 1 456
无人共我
无人共我 2021-01-14 07:15

Let\'s assume, Bob has wrapped his library into the namespace \"bob\", and Alice is going to make the whole namespace visible insi

相关标签:
1条回答
  • 2021-01-14 07:21

    Let's suppose that Alice is using two libraries, made by Bob and Charlie.

    // This file is written by Alice:
    #include <bob.hpp>
    #include <charlie.hpp>
    
    int main(void) {
      using namespace bob;
      using namespace charlie;
    
      // Do a bunch of stuff
    }
    

    Now, Charlie invents a new feature called foobar which he adds to his library. foobar is great and his users like it. Alice starts using it also.

    Then Bob says, "I like foobar also, I want to have my own foobar I can use in my library. But I don't want a dependency on Charlie." So he creates his own version.

    Uh oh, now Alice's code doesn't compile! Every usage of foobar in Alice's code is ambiguous and she has to rewrite her whole project.

    Then, the same thing happens next month. And the next month after that.

    Now, all of Alice's customers are really unhappy because they are building large technologies and trying to keep up-to-date versions of their dependencies, but every time they try to upgrade anything, Alice's code craps out. They make a lot of bug reports on her bug tracker.

    Alice sends an email to Bob and Charlie and says

    Guys, you have to stop making classes with the same names, or I'm going to lose all of my business!

    Bob and Charlie send an email back to Alice:

    No Alice, you need to stop putting using namespace bob; and using namespace charlie; in your code. That is not supported by Bob or by Charlie.


    Now, let's tell the same story again, except there is no Charlie. It's just Alice making her own classes in her project, colliding with new names added by Bob.


    In short, a using namespace directive is never a good idea (in my opinion). Especially when the namespace is an external library. You don't really know how that namespace can change in the future, and if it changes in a way that is at all bad for you, you suddenly have a huge mess on your hands.

    Using namespace = to shorten namespaces is often a very good idea. I like to do the following:

    namespace my_lib {
    
    namespace qi = boost::spirit::qi;
    
    // Do stuff with qi
    // ...
    
    } // end namespace my_lib
    

    That way I get to use the short name qi in my_lib, but I don't impose anything on my users. (Who I expect will not be doing using namespace my_lib;!)

    If you are a user, you could do something like

    namespace cha = charlie::name::space::is_way_too_long;
    

    But, you should be more than happy to type short namespaces like bob:: or std::, whether you are a user or a library implementor, if it will mean that your code doesn't break when the libraries are upgraded.

    This is not about DRY. Putting some kind of qualifier on names makes it much easier to read your code and understand what it means.

    For instance look at SDL, a popular C library. To my knowledge, every macro in SDL begins SDL_ and every function begins sdl_. Is that a violation of "DRY"? No. There are no duplicated implementation details here -- the common prefix is there to avoid name collisions. Also, it makes the code more readable and maintainable -- whenever I see a symbol that is talking about an SDL entity I know so immediately. It's very helpful to both the humans and the computers.

    Putting using namespace std; or using namespace my_lib; is like taking one of C++ best features and throwing it in the garbage. The trade-off is, save yourself typing 5 characters, at the cost of making a great harm to readability and maintainability.


    Parting thought: How does using namespace affect the quality of the error messages that you get.

    Here's a simple program that doesn't compile:

    #include <iostream>
    
    struct foo {};
    
    int main() {
      std::cout << foo{} << std::endl;
    }
    

    When the compiler sees this code, it is going to have to try every stream operator overload that it knows about and check if foo is convertible to any of those things. Because std::cout is one of the arguments, ADL means that we have to search the entire std namespace. Turns out, surprise surprise, foo isn't convertible to any of those things. On gcc 5.3 I get the following (200 line) error message.

    main.cpp: In function ‘int main()’:
    main.cpp:6:13: error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘foo’)
       std::cout << foo{} << std::endl;
                 ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:628:5: note: candidate: std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = foo] <near match>
         operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
         ^
    /usr/include/c++/5/ostream:628:5: note:   conversion of argument 1 would be ill-formed:
    main.cpp:6:20: error: cannot bind ‘std::ostream {aka std::basic_ostream<char>}’ lvalue to ‘std::basic_ostream<char>&&’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:108:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ostream_type& (*)(std::basic_ostream<_CharT, _Traits>::__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(__ostream_type& (*__pf)(__ostream_type&))
           ^
    /usr/include/c++/5/ostream:108:7: note:   no known conversion for argument 1 from ‘foo’ to ‘std::basic_ostream<char>::__ostream_type& (*)(std::basic_ostream<char>::__ostream_type&) {aka std::basic_ostream<char>& (*)(std::basic_ostream<char>&)}’
    /usr/include/c++/5/ostream:117:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ios_type& (*)(std::basic_ostream<_CharT, _Traits>::__ios_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>; std::basic_ostream<_CharT, _Traits>::__ios_type = std::basic_ios<char>]
           operator<<(__ios_type& (*__pf)(__ios_type&))
           ^
    /usr/include/c++/5/ostream:117:7: note:   no known conversion for argument 1 from ‘foo’ to ‘std::basic_ostream<char>::__ios_type& (*)(std::basic_ostream<char>::__ios_type&) {aka std::basic_ios<char>& (*)(std::basic_ios<char>&)}’
    /usr/include/c++/5/ostream:127:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::ios_base& (*)(std::ios_base&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(ios_base& (*__pf) (ios_base&))
           ^
    /usr/include/c++/5/ostream:127:7: note:   no known conversion for argument 1 from ‘foo’ to ‘std::ios_base& (*)(std::ios_base&)’
    /usr/include/c++/5/ostream:166:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(long __n)
           ^
    /usr/include/c++/5/ostream:166:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long int’
    /usr/include/c++/5/ostream:170:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(unsigned long __n)
           ^
    /usr/include/c++/5/ostream:170:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long unsigned int’
    /usr/include/c++/5/ostream:174:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(bool) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(bool __n)
           ^
    /usr/include/c++/5/ostream:174:7: note:   no known conversion for argument 1 from ‘foo’ to ‘bool’
    In file included from /usr/include/c++/5/ostream:638:0,
                     from /usr/include/c++/5/iostream:39,
                     from main.cpp:1:
    /usr/include/c++/5/bits/ostream.tcc:91:5: note: candidate: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short int) [with _CharT = char; _Traits = std::char_traits<char>]
         basic_ostream<_CharT, _Traits>::
         ^
    /usr/include/c++/5/bits/ostream.tcc:91:5: note:   no known conversion for argument 1 from ‘foo’ to ‘short int’
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:181:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(short unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(unsigned short __n)
           ^
    /usr/include/c++/5/ostream:181:7: note:   no known conversion for argument 1 from ‘foo’ to ‘short unsigned int’
    In file included from /usr/include/c++/5/ostream:638:0,
                     from /usr/include/c++/5/iostream:39,
                     from main.cpp:1:
    /usr/include/c++/5/bits/ostream.tcc:105:5: note: candidate: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(int) [with _CharT = char; _Traits = std::char_traits<char>]
         basic_ostream<_CharT, _Traits>::
         ^
    /usr/include/c++/5/bits/ostream.tcc:105:5: note:   no known conversion for argument 1 from ‘foo’ to ‘int’
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:192:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(unsigned int __n)
           ^
    /usr/include/c++/5/ostream:192:7: note:   no known conversion for argument 1 from ‘foo’ to ‘unsigned int’
    /usr/include/c++/5/ostream:201:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long long int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(long long __n)
           ^
    /usr/include/c++/5/ostream:201:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long long int’
    /usr/include/c++/5/ostream:205:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long long unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(unsigned long long __n)
           ^
    /usr/include/c++/5/ostream:205:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long long unsigned int’
    /usr/include/c++/5/ostream:220:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(double) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(double __f)
           ^
    /usr/include/c++/5/ostream:220:7: note:   no known conversion for argument 1 from ‘foo’ to ‘double’
    /usr/include/c++/5/ostream:224:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(float) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(float __f)
           ^
    /usr/include/c++/5/ostream:224:7: note:   no known conversion for argument 1 from ‘foo’ to ‘float’
    /usr/include/c++/5/ostream:232:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long double) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(long double __f)
           ^
    /usr/include/c++/5/ostream:232:7: note:   no known conversion for argument 1 from ‘foo’ to ‘long double’
    /usr/include/c++/5/ostream:245:7: note: candidate: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(const void*) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
           operator<<(const void* __p)
           ^
    /usr/include/c++/5/ostream:245:7: note:   no known conversion for argument 1 from ‘foo’ to ‘const void*’
    In file included from /usr/include/c++/5/ostream:638:0,
                     from /usr/include/c++/5/iostream:39,
                     from main.cpp:1:
    /usr/include/c++/5/bits/ostream.tcc:119:5: note: candidate: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__streambuf_type*) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__streambuf_type = std::basic_streambuf<char>]
         basic_ostream<_CharT, _Traits>::
         ^
    /usr/include/c++/5/bits/ostream.tcc:119:5: note:   no known conversion for argument 1 from ‘foo’ to ‘std::basic_ostream<char>::__streambuf_type* {aka std::basic_streambuf<char>*}’
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:574:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, const unsigned char*)
         operator<<(basic_ostream<char, _Traits>& __out, const unsigned char* __s)
         ^
    /usr/include/c++/5/ostream:574:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const unsigned char*’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:569:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, const signed char*)
         operator<<(basic_ostream<char, _Traits>& __out, const signed char* __s)
         ^
    /usr/include/c++/5/ostream:569:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const signed char*’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:556:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, const char*)
         operator<<(basic_ostream<char, _Traits>& __out, const char* __s)
         ^
    /usr/include/c++/5/ostream:556:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const char*’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/ostream:638:0,
                     from /usr/include/c++/5/iostream:39,
                     from main.cpp:1:
    /usr/include/c++/5/bits/ostream.tcc:321:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const char*)
         operator<<(basic_ostream<_CharT, _Traits>& __out, const char* __s)
         ^
    /usr/include/c++/5/bits/ostream.tcc:321:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const char*’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:539:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const _CharT*)
         operator<<(basic_ostream<_CharT, _Traits>& __out, const _CharT* __s)
         ^
    /usr/include/c++/5/ostream:539:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   mismatched types ‘const _CharT*’ and ‘foo’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:519:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, unsigned char)
         operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)
         ^
    /usr/include/c++/5/ostream:519:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘unsigned char’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:514:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, signed char)
         operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
         ^
    /usr/include/c++/5/ostream:514:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘signed char’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:508:5: note: candidate: template<class _Traits> std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, char)
         operator<<(basic_ostream<char, _Traits>& __out, char __c)
         ^
    /usr/include/c++/5/ostream:508:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘char’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:502:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, char)
         operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
         ^
    /usr/include/c++/5/ostream:502:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘char’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/iostream:39:0,
                     from main.cpp:1:
    /usr/include/c++/5/ostream:497:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, _CharT)
         operator<<(basic_ostream<_CharT, _Traits>& __out, _CharT __c)
         ^
    /usr/include/c++/5/ostream:497:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   deduced conflicting types for parameter ‘_CharT’ (‘char’ and ‘foo’)
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/bits/ios_base.h:46:0,
                     from /usr/include/c++/5/ios:42,
                     from /usr/include/c++/5/ostream:38,
                     from /usr/include/c++/5/iostream:39,
                     from main.cpp:1:
    /usr/include/c++/5/system_error:209:5: note: candidate: template<class _CharT, class _Traits> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::error_code&)
         operator<<(basic_ostream<_CharT, _Traits>& __os, const error_code& __e)
         ^
    /usr/include/c++/5/system_error:209:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   cannot convert ‘foo{}’ (type ‘foo’) to type ‘const std::error_code&’
       std::cout << foo{} << std::endl;
                        ^
    In file included from /usr/include/c++/5/string:52:0,
                     from /usr/include/c++/5/bits/locale_classes.h:40,
                     from /usr/include/c++/5/bits/ios_base.h:41,
                     from /usr/include/c++/5/ios:42,
                     from /usr/include/c++/5/ostream:38,
                     from /usr/include/c++/5/iostream:39,
                     from main.cpp:1:
    /usr/include/c++/5/bits/basic_string.h:5172:5: note: candidate: template<class _CharT, class _Traits, class _Alloc> std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&)
         operator<<(basic_ostream<_CharT, _Traits>& __os,
         ^
    /usr/include/c++/5/bits/basic_string.h:5172:5: note:   template argument deduction/substitution failed:
    main.cpp:6:20: note:   ‘foo’ is not derived from ‘const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>’
       std::cout << foo{} << std::endl;
                        ^
    

    Here's the point: If you do using namespace bob;, then every one of bob's types that is streamable is also going to appear in that list! If you do using namespace charlie; then all of his types will be there too!

    Not only will the error messages be worse, there's a greater chance that you can get some really bizarre interaction that you didn't expect. What if Bob's types are occasionally streamable into one of Charlie's types? And Charlie's types are occasionally implicitly convertible to some standard type that is streamable?

    And of course this all applies not just to any operator overload, but any template or function call.

    So, bottom line, C++ is a lot easier to reason about and works a lot better if you avoid mixing lots of crap together in one namespace.

    0 讨论(0)
提交回复
热议问题