Header file best practices for typedefs

前端 未结 5 1310
温柔的废话
温柔的废话 2020-12-13 01:51

I\'m using shared_ptr and STL extensively in a project, and this is leading to over-long, error-prone types like shared_ptr< vector< shared_ptr

5条回答
  •  囚心锁ツ
    2020-12-13 02:23

    I'm using shared_ptr and STL extensively in a project, and this is leading to over-long, error-prone types like shared_ptr< vector< shared_ptr > > (I'm an ObjC programmer by preference, where long names are the norm, and still this is way too much.) It would be much clearer, I believe, to consistently call this FooListPtr and documenting the naming convention that "Ptr" means shared_ptr and "List" means vector of shared_ptr.

    for starters, i recommend using good design structures for scoping (e.g., namespaces) as well as descriptive, non-abbreviated names for typedefs. FooListPtr is terribly short, imo. nobody wants to guess what an abbreviation means (or be surprised to find Foo is const, shared, etc.), and nobody wants to alter their code simply because of scope collisions.

    it may also help to choose a prefix for typedefs in your libraries (as well as other common categories).

    it's also a bad idea to drag types out of their declared scope:

    namespace MON {
    namespace Diddy {
    class Foo;
    } /* << Diddy */
    
    /*...*/
    typedef Diddy::Foo Diddy_Foo;
    
    } /* << MON */
    

    there are exceptions to this:

    • an entirely ecapsualted private type
    • a contained type within a new scope

    while we're at it, using in namespace scopes and namespace aliases should be avoided - qualify the scope if you want to minimize future maintentance.

    This is easy to typedef, but it's causing headaches with the headers. I seem to have several options of where to define FooListPtr:

    Foo.h. That entwines all the headers and creates serious build problems, so it's a non-starter.

    it may be an option for declarations which really depend on other declarations. implying that you need to divide packages, or there is a common, localized interface for subsystems.

    FooFwd.h ("forward header"). This is what Effective C++ suggests, based on iosfwd.h. It's very consistent, but the overhead of maintaining twice the number of headers seems annoying at best.

    don't worry about the maintenance of this, really. it is a good practice. the compiler uses forward declarations and typedefs with very little effort. it's not annoying because it helps reduce your dependencies, and helps ensure that they are all correct and visible. there really isn't more to maintain since the other files refer to the 'package types' header.

    Common.h (put all of them together into one file). This kills reusability by entwining a lot of unrelated types. You now can't just pick up one object and move it to another project. That's a non-starter.

    package based dependencies and inclusions are excellent (ideal, really) - do not rule this out. you'll obviously have to create package interfaces (or libraries) which are designed and structured well, and represent related classes of components. you're making an unnecessary issue out of object/component reuse. minimize the static data of a library, and let the link and strip phases do their jobs. again, keep your packages small and reusable and this will not be an issue (assuming your libraries/packages are well designed).

    Some kind of fancy #define magic that typedef's if it hasn't already been typedefed. I have an abiding dislike for the preprocessor because I think it makes it hard for new people to grok the code, but maybe....

    actually, you may declare a typedef in the same scope multiple times (e.g., in two separate headers) - that is not an error.

    declaring a typedef in the same scope with different underlying types is an error. obviously. you must avoid this, and fortunately the compiler enforces that.

    to avoid this, create a 'translation build' which includes the world - the compiler will flag declarations of typedeffed types which don't match.

    trying to sneak by with minimal typedefs and/or forwards (which are close enough to free at compilation) is not worth the effort. sometimes you'll need a bunch of conditional support for forward declarations - once that is defined, it is easy (stl libraries are a good example of this -- in the event you are also forward declaring templateclass vector;).

    it's best to just have all these declarations visible to catch any errors immediately, and you can avoid the preprocessor in this case as a bonus.

    Use a vector subclass rather than a typedef. This seems dangerous...

    a subclass of std::vector is often flagged as a "beginner's mistake". this container was not meant to be subclassed. don't resort to bad practices simply to reduce your compile times/dependencies. if the dependency really is that significant, you should probably be using PIMPL, anyways:

    // .types.hpp
    namespace MON {
    class FooListPtr;
    }
    
    // FooListPtr.hpp
    namespace MON {
    class FooListPtr {
        /* ... */
    private:
        shared_ptr< vector< shared_ptr > > d_data;
    };
    }
    

    Are there best practices here? How do they turn out in real code, when reusability, readability and consistency are paramount?

    ultimately, i've found a small concise package based approach the best for reuse, for reducing compile times, and minimizing dependence.

提交回复
热议问题