You are correct that anything STL - actually, anything from any third party library which is templated - is best avoided in any public C++ API. You also want to follow the long list of rules at http://www.ros.org/reps/rep-0009.html#definition to inhibit ABI breakage which makes programming public C++ APIs a chore.
And the answer regarding C++11 is no, this standard isn't touching that. More interesting is why not? The answer is because C++17 is very much touching that, and for C++ Modules to be implemented we need exported templates to work, and for that we need a LLVM type compiler such as clang which can dump the full AST to disc and then do caller-dependent lookups to handle the many ODR violating cases in any large C++ project - which, by the way, includes lots of GCC and ELF code.
Lastly, I see a lot of MSVC hate and pro-GCC comments. These are very misinformed - GCC on ELF is fundamentally, and irretrievably, incapable of producing valid and correct C++ code. The reasons for this are many and legion, but I'll quickly quote one case example: GCC on ELF cannot safely produce Python extensions written using Boost.Python where more than one extension based on Boost.Python is loaded into Python. That's because ELF with its global C symbol table is simply incapable by design of preventing ODR violations causing segfaults, whereas PE and MachO and indeed the proposed C++ Modules specification all use per-module symbol tables - which incidentally also means vastly faster process init times. And there are plenty more problems: see a StackOverflow I answered recently at https://stackoverflow.com/questions/14268736/symbol-visibility-exceptions-runtime-error/14364055#14364055 for example where C++ exception throws are irretrievably fundamentally broken on ELF.
Last point: regarding interoping different STLs, this is a big pain for many large corporate users trying to mix third party libraries which are tightly integrated to some STL implementation. The only solution is a new mechanism for C++ to handle STL interop, and while they're at it you might as well fix compiler interop too so you can (for example) mix MSVC, GCC and clang compiled object files and it all just works. I'd watch the C++17 effort and see what turns up there in the next few years - I'd be surprised if nothing does.
I think these implementations are reasonable and a generally good solution. Adding an appropriate move constructor and move assignment may help deal with your copy concerns - the default should be appropriate with the shared wrapper.
Some may argue (or advise) that you do not need to wrap the Standard Library facilities that you use here; whilst this is true, the semantics of the getData()
function may be quite specific for your target code - using the library facility or wrapping it is really a design tradeoff.
You may need a few tweaks on the assignment operators; and possibly be more explicit on the other special member functions (I would favour this here - it makes the intent of the code clearer). By way of example;
#include <library>
class UniqueWrapper {
private:
lib_type* data;
public:
UniqueWrapper() : data(library_new()) {}
UniqueWrapper(const UniqueWrapper&) = delete;
UniqueWrapper& operator=(const UniqueWrapper&) = delete;
UniqueWrapper(UniqueWrapper&&) = delete;
UniqueWrapper& operator=(UniqueWrapper&&) = delete;
~UniqueWrapper() {
library_free(data);
}
const lib_type* getData() const {
return data;
}
};
And as a shared wrapper;
#include <library>
class Deleter {
public:
void operator()(lib_type* p) const {
library_free(p);
}
};
class SharedWrapper {
private:
shared_ptr<lib_type> data;
public:
SharedWrapper() : data(library_new(), Deleter()) {}
SharedWrapper(const SharedWrapper& orig) = default;
SharedWrapper& operator=(const SharedWrapper& orig) = default;
SharedWrapper(SharedWrapper&& orig) = default;
SharedWrapper& operator=(SharedWrapper&& orig) = default;
~SharedWrapper() = default;
const lib_type* getData() const {
return data.get(); // a reference return could also be used.
}
const lib_type& getDataAlt() const {
// alternative for a reference
return *data;
}
};
On your last question;
I am particularly worried about the getData implementation; whether I should return a copy of the shared pointer or a naked pointer and why?
When you expose some of the internals of an object, you give up some control over how that data is used - essentially this is not a bad thing, you just need to bear that in mind.
Having said that, there are ways you can make the code easy to use properly and hard to use incorrectly. Returning a const
in this case would probably be advised, since the method is const
- it is saying to the client of your code, don't change it. If changes are to be allowed, then a non-const
version is also required.
The wrapper class you have is there to manage the resource, so I would advise returning a reference (hence for observation) over a pointer; a pointer can be a nullptr
so if there is an "optional" nature to the value, a nullptr
pointer can be used to indicate that.
If the shared and const-ness of the return type is to be maintained, David Hammen's solution of shared_ptr<const lib_type>
is neat.
Best Answer
So why don't you use C++ instead of C?
is most probably what you want. And even when programming in C, do yourself a favor and avoid to use fixed limits like
MAX_ITEMS_SIZE
, that number will almost be too big (wasting resources) or too small (which means your program might not work as intended).