I'm working on a library that is somewhat complex. It offers a bunch of DLL_EXPORT
ed functions which use some custom struct
s. 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:
//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.)
//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:
#include
halfway through my headerDisadvantages:
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
//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:
MyLibraryInterface_v1::MyDataType
instead of MyLibraryInterface_v1_Types::MyDataType
, which is more intuitive if they're calling a function in MyLibraryInterface_v1
anywayDisadvantages:
#include
at the very bottom of the header looks really bad
Mixing datatypes and function declarations seems a bit iffy to me
//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:
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 class
es) 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
).
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.