Question

I'm working on a library that is somewhat complex. It offers a bunch of DLL_EXPORTed functions which use some custom structs. At the moment, those structs are defined with the functions that use them, like so:

struct MyDataType
{
  std::wstring name;
  std::wstring purpose;
}

DLL_EXPORT int DoSomething(MyDataType instruction);

For maintainability purposes, I'm gradually switching that library to the more standard interface style:

struct MyLibraryInterface
{
  virtual int DoSomething(MyDataType instruction) = 0;
}

To make the interface side of things a little easier, I'm working with a 3rd-party library. This library requires I do a little bit of setup work for each custom datatype I use, and this is where I'm uncertain as to how to proceed. I see a few different ways I could do it:

Method 1

//MyLibraryInterface.h
namespace MyLibraryInterface_v1_Types
{
  struct MyDataType
  {
    std::wstring name;
    std::wstring purpose;
  }
}
#include "MyLibraryInterface_v1_Types_Backend.private.h" //this header would contain the required "paperwork" to make my datatype work with the 3rd-party lib

struct MyLibraryInterface_v1
{
  virtual int DoSomething(MyDataType instruction) = 0;
}

Advantages:

  • Anyone who needs to use my lib just has to #include one header

  • Custom namespace keeps datatypes separate from function definitions

Disadvantages:

  • The #include halfway through the header looks sloppy and out-of-place (although I have noted that certain MS headers use this technique)

  • Is a custom namespace really that necessary, or am I just confusing users? (The encapsulation itself is definitely necessary, since I might change the definition of this datatype down the road. But I don't know if I need a custom namespace or if I should just put the datatype declaration in the declaration of the struct that will use it.)

Method 2

//MyLibraryInterface.h
namespace MyLibraryInterface_v1_Types
{
  struct MyDataType
  {
    std::wstring name;
    std::wstring purpose;
  }
}
//3rd-party "paperwork" is directly placed here

struct MyLibraryInterface_v1
{
  virtual int DoSomething(MyDataType instruction) = 0;
}

Advantages:

  • No messy-looking #include halfway through my header

Disadvantages:

  • 3rd-party paperwork now shows up in the header my users will be using (they don't need to see it and I'd rather they not, for cosmetic/ease-of-understanding reasons)

  • This feels like I'm not taking advantage of the datatype namespace's power at all, since the 3rd-party code is left free-floating in my library's code and not encapsulated

Method 3

//MyLibraryInterface.h
struct MyLibraryInterface_v1
{
  struct MyDataType
  {
    std::wstring name;
    std::wstring purpose;
  }

  virtual int DoSomething(MyDataType instruction) = 0;
}

#include "MyLibraryInterface_v1_Types_Backend.private.h" //this header would contain the required "paperwork" to make my datatype work with the 3rd-party lib

Advantages:

  • Gets rid of the custom namespace
  • Less confusion because users are now referring to MyLibraryInterface_v1::MyDataType instead of MyLibraryInterface_v1_Types::MyDataType, which is more intuitive if they're calling a function in MyLibraryInterface_v1 anyway

Disadvantages:

  • #include at the very bottom of the header looks really bad

  • Mixing datatypes and function declarations seems a bit iffy to me

Method 4

//MyLibraryInterface_v1_Types.h
namespace MyLibraryInterface_v1_Types
{
  struct MyDataType
  {
    std::wstring name;
    std::wstring purpose;
  }
}
//3rd-party paperwork can be directly placed here, immediately following the definition of the custom datatype


//MyLibraryInterface.h
#include "MyLibraryInterface_v1_Types.h" /* this header, as defined above, holds the definitions of the custom datatypes this library will use. It also includes the 3rd-party paperwork required to make those datatypes work. It can't be a private header, though, because users will need to access it to use the custom types. */

struct MyLibraryInterface_v1
{
  virtual int DoSomething(MyDataType instruction) = 0;
}

Advantages:

  • 3rd-party paperwork goes directly with the declarations of the datatypes that need it

Disadvantages:

  • Users may have a hard time finding or using the custom datatypes

  • Feels pretty unintuitive, since the datatypes reside in both a separate header and a separate namespace


So which is best? Am I overlooking a different, better, method entirely? Or am I simply going to have to bite the bullet and accept that, no matter which way I decide to go with this, I'll have some problems to face.


Update with a bit more information:

The 3rd-party library I'm using wraps my interface in a struct for me. So I'll be able to create an object of MyLibraryInterface*, the 3rd-party library will let me access an implementation of that interface from a specified DLL, and then I can call MyLibraryObj->DoSomething(). This essentially is a variant of pImpl.

This 3rd-party library also automatically wraps any STL types and any custom datatypes so they can be used across multiple compilers, so my std::wstring usage is completely safe here. However, the library requires that I provide certain setup information for how to wrap custom types. I have to provide that setup information somewhere after each custom type is defined, which bars the "normal" pattern of putting an #include with the private setup info at the top of my interface header. I also can't remove the private setup information from the interface header entirely; anyone who calls my library via this interface will have to use the 3rd-party library to do so, and they'll need to provide the declaration of the interface again so the library knows what it's looking for in a given DLL. All I can do is try to make the private setup work look as neat and unintrusive as possible, as well as ideally marking it as something my library's users will never need or want to work directly with.

Additionally, I have the option of putting my custom datatypes into the interface struct or into their own namespace. I toyed with putting them directly in the struct at first, but since some of these datatypes are constant data (enum classes) it seemed a bit sloppy to put them in the struct with the function declarations. A namespace "felt" cleaner, but with the downside that functions and datatypes would be treated differently (myLibraryObj->DoSomething() vs MyLibraryInterface_v1_Types::MyDataType) and therefore might be less intuitive than keeping everything in the struct (myLibraryObj->DoSomething(), MyLibraryInterface_v1::MyDataType).

Was it helpful?

Solution

If you're making a library for others to consume, always wrap it in a namespace. Make it adequately long to be fully descriptive. If someone wants a shorter name to use in qualified names, they can define a namespace alias for their own use. Also, you need not worry about what the header looks like inside. If you do a good job with documentation, no one will need to look at the header.

Namespaces can be nested, and you can use this to shield (but not hide) implementation details. One frequently used convention is to call such a namespace detail. Write documentation that indicates that this namespace is not for public use and contains details subject to change.

Recall that #include is a purely textual mechanism, simply substituting a block of text for the directive. Thus, if you include an external header inside your detail namespace, it won't appear in the global namespace nor in the top-level library namespace. By including the external definitions this way, you can explicitly expose only what you need from the external header; everything else is shielded inside detail otherwise.

The example below illustrates these principles. You can suspend disbelief and assume that external_library is defined inside some external header file. This example illustrates each of the principles outlined above. I am presuming that you need the external library as part of the definition of some of your types; if not, it shouldn't be in the header at all.

namespace library {
    namespace detail {
        #include <whatever>
        namespace external_library {
            class exposed {} ;
            class hidden {} ;
        }
    }
    typedef detail::external_library::exposed external_type ;
    class my_type {} ;
}
library::my_type foo ;
library::external_type bar ;

I didn't address the external linkage issues you raised, because they are separate from the scoping issues that are central to your question.

OTHER TIPS

I don't think you should have a separate namespace to hold your library's types. As a user, I like that, when using library NiftyLibrary, all of its entities (i.e. types, functions, and the like) are contained within a namespace named Nifty or something like that. Of course, this namespace could in turn contain other namespaces if the library is too big, but you get the idea. I find it weird to refer to namespace NiftyTypes when using one of NiftyLibrary's types and to Nifty otherwise. It may be the case that a type's identifier collides with that of a function, but then you're doing something fundamentally wrong.

Is it absolutely necessary to expose an implementation detail such as this third-party library you mention to your end users? You have to take into account that, when they #include your headers, they'll be also #includeing those, so compilation performance will decrease and, most important of all, some of the identifiers used by the users might clash with those used by the third-party library. An alternative would be to declare all of the latter inside of a detail namespace, as Boost's libraries do. Still, not the cleanest choice in my opinion. Can't you move the setup associated with your custom datatypes out of your headers? It might not be possible if it need to actually do something with the type, but give it a shot. (Have you heard of the PIMPL idiom?)

I would apply next rules for library development:

  1. It is a good idea to use namespace, make it shorter. I would recommend no more than 5 letters. This way if the consumer of the library has the same type name (I encountered such cases in my carreer) he will be able to distinguish between your types and his.
  2. Place all your definitions under that namespace - types + interfaces.
  3. Do not include private headers in the public one. Include the private headers in the source files.
  4. Using STL in your interface (wstring) will limit the client of your library to use the same compiler (VC or gcc) and the same version of it. a. I would check which compiler your clients will use and use this compiler for your library. How many clients will you have? Are they big enterprises or a small ones? b. Another option - provide 2 versions of your library - VC and gcc. c. In case you will have to support too many compilers types and versions in the future - better to work with plain type - array of wchar_t (but I think this the least preferred option here)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top