Why is namespace composition so rarely used?

前端 未结 4 1718
不思量自难忘°
不思量自难忘° 2021-02-18 23:57

In his book The C++ Programming Language (third edition) Stroustrup teaches to define individual components in their own namespace and import them in a general namespac

相关标签:
4条回答
  • 2021-02-19 00:08

    I guess because it reduces encapsulation. In your example, it's going to be a right pain when you write map_api to go to the api.h and import it into the api namespace. Having one big header api.h that imports every sub-namespace is not exactly include minimization.

    • map.h:

      namespace map_api { /* fns */ }
      
    • api.h:

      #include <map.h>
      namespace api { using map_api::map; }
      

    Or, considering other header layouts: if the imports into the api namespace happen in another file:

    • map_internal.h:

      namespace map_api { /* fns */ }
      
    • map_api.h:

      #include <map_internal.h>
      namespace api { using map_api::map; }
      

    That's pretty painful too, having to split out the api into two files like that.

    The final possibility is doing it all in one file:

    • map.h:

      namespace map_api { /* fns */ }
      namespace api { using map_api::map; }
      

    In that case, what's the point I wonder of putting things in two namespaces that are on the same level of abstraction, if they're both equally exposed to all clients/includers..?

    0 讨论(0)
  • 2021-02-19 00:16

    Simply because nobody learns it. Most programmers are taught Java-style OOP, and it's more common to see C++ code which encapsulates a namespace-style API into a class.

    C++ has argument-dependent function lookup (ADL), which allows it to select a function from an API based on the namespaces of the types of the arguments it's called with. It's a powerful mechanism which allows you to bypass much of the OOP boilerplate yet retain the benefits. But it's not really taught to beginners, for no good particular reason.

    For what it's worth, your example is roughly equivalent to this condensed version:

    namespace api {
    
    struct array {
        friend void print(const array&) { }
    };
    
    struct list {       
        friend void print(const list&) { }
    };
    
    }
    

    When you define a function as a friend within a class, it belongs not to the class, but the enclosing namespace. Moreover, its name is available only within the class, not the enclosing namespace, except when it's called using the class as an argument.

    Polluting the global namespace with array_api and list_api namespaces is a bad idea. Better to make a hierarchy as in my example.

    So, given the above, you could do this:

    api::array x;
    
    print( x ); // ADL finds api::print defined inside api::array
    

    Indeed, this is how the iostream-style operator << works. You don't need a using declaration (or directive) to print objects from a library.

    0 讨论(0)
  • 2021-02-19 00:20

    Mostly I wonder what the benefits would be (as Raymond Chen says, every feature starts with -100 points). However, I have a counterpoint to offer: Luabind, which does use something that looks like this. See luabind/object.hpp, which essentially says:

    namespace luabind {
      namespace adl {
        class object {
        };
      }
      using adl::object;
    }
    

    From the name alone we can infer the motivation: to support argument-dependent lookup. Given what the user knows as a luabind::object, which is actually a luabind::adl::object, related functions will be discovered automatically by the compiler from the luabind::adl namespace. Yet those functions, which the user may not need to know about in a very explicit way, do not "pollute" the main luabind namespace. So that's nice, I guess.

    But you know what's not nice? Forward declaring one of these classes. This fails:

    namespace luabind { class object; }
    

    You need to do this instead:

    namespace luabind { namespace adl { class object; } }
    

    Thus the abstraction quickly leaks, as the user does need to know about this magic adl namespace after all.

    0 讨论(0)
  • 2021-02-19 00:25

    I feel the other answers have left out the first, simplest, and most useful way to compose namespaces: to use only what you need.

    namespace Needed {                                                                                                                                                                            
      using std::string;                                                                                                                                                                          
      using std::bind;                                                                                                                                                                            
      using std::function;                                                                                                                                                                        
      using std::cout;                                                                                                                                                                            
      using std::endl;                                                                                                                                                                            
      using namespace std::placeholders;                                                                                                                                                          
    }                                                                                                                                                                                             
    
    
    int main(int argc, char* argv[])                                                                                                                                                              
    {                                                                                                                                                                                             
    
      /*  using namespace std;                                                                                                                                                                    
          would avoid all these individual using clauses,                                                                                                                     
          but this way only these are included in the global                                                                                                                                      
          namespace.                                                                                                                                                                          
      */                                                                                                                                                                                          
    
     using namespace Needed;  // pulls in the composition
    
     string s("Now I have the namespace(s) I need,");
    
     string t("But not the ones I don't.");
    
     cout << s << "\n" << t << endl;                                                                                               
    
     // ...
    

    Thus any conflicts with other parts of the STL are avoided,as well as the need to preface the common used functions with std::.

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