How can I avoid exposing thirdparty types in my API classes?

Started by
5 comments, last by wintertime 10 years, 6 months ago

Hi,
Its been long since the last time I used C++. Only played a little bit with it but now that I want to become serious in game dev I've decided to retake it.

I'm designing an API for a simple file packer (you know, like a tar file manager) intended to create packages to put game resources into it. This API will be used by the frontends to view, add, delete and extract/access a file from the package. The problem I'm facing is that I don't want the frontends to include the thirdparty dependencies that I needed for the library that has the API. So, I've made the method declarations (the public interface) in a way that only ask for basic types and std objects. But, the implementation has objects that have types that come from the boost library, that makes the "private:" section of the class declaration to have thirdparty library tipes and I don't want to have it, lets say in my Qt frontend to include boost when I should not need it.

I was reading "Effective C++ 2nd Edition" and in "Item 34: Minimize compilation dependencies between files" it says a good way to reduce dependencies is to have 2 physical classes (to name them someway) for each logical class, one to expose the public part of the class, and another with the private part of the class, having the public class a pointer to an object of the private class as a member.

Would it help me this to avoid having to include boost in my frontend code? Is there a more simple way to do it? I tried by writting forward declarations but some boost classes (one of them I'm using is unordered_map) have very difficult template declarations with other dependencies in it.

Thanks

Advertisement


I was reading "Effective C++ 2nd Edition" and in "Item 34: Minimize compilation dependencies between files" it says a good way to reduce dependencies is to have 2 physical classes (to name them someway) for each logical class, one to expose the public part of the class, and another with the private part of the class, having the public class a pointer to an object of the private class as a member.

Quite correct, that's the PIMPL Idiom (pointer to implementation). It's the best way to hide your implementation and reduce dependencies.

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty
Pimpl what you can, forward-declare what you can't (i.e. things in function signatures).

I don't bother Pimpling every class, but some of the more commonly used and larger classes get Pimpl'd.

I Pimpl like this:

MyClass.h
#include "Common/Assorted/Pimpl.h"

class MyClass
{
public:
	//...stuff...
	
private:
private:
	struct Impl;
	Pimpl<Impl> pImpl;
};
MyClass.cpp
#include "Common/Assorted/Pimpl_Impl.h"

struct MyClass::Impl
{
	Impl(const std::string &str);
	std::string stuff;
	int blah = 357;
};

//A constructor for the impl is completely optional.
MyClass::Impl::Impl(const std::string &str)
{
    stuff = str;
}

MyClass::MyClass() : pImpl("Test")
{
	std::cout << pImpl->stuff << std::endl;
}
Pimpl.h
#ifndef COMMON_ASSORTED_PIMPL_H
#define COMMON_ASSORTED_PIMPL_H

/*
	This file provides the declaration of the Pimpl class,
	and should only be #included by the .h file of the class being Pimpl'd.
	
	The Pimpl_Impl.h class should be #included by the .cpp file of the class being Pimpl'd.
*/

template<typename Type>
class Pimpl
{
public:
	//Default constructor.
	Pimpl();
	//Multi-argument constructor.
	template<typename ...Args>
	Pimpl(Args &&...);

	//Copy constructor.
	Pimpl(const Pimpl &other);
	//Copy-assignment operator.
	Pimpl &operator=(const Pimpl&);

	//Move constructor.
	Pimpl(Pimpl &&other);
	//Move-assignment operator.
	Pimpl &operator=(Pimpl&&);

	//Destructor.
	~Pimpl();
	
	//Non-const access:
	Type *operator->();
	Type *operator*();
	
	//Const access:
	const Type *operator->() const;
	const Type *operator*() const;
	
private:
	//The pointer to the implementation of the class being Pimpl'd.
	Type *data = nullptr;
};

#endif // COMMON_ASSORTED_PIMPL_H
Pimpl_Impl.h
#ifndef COMMON_ASSORTED_PIMPL_IMPLEMENTATION_H
#define COMMON_ASSORTED_PIMPL_IMPLEMENTATION_H

/*
	This file provides the implementation of the Pimpl class,
	and should only be #included by the .cpp file of the class being Pimpl'd.
*/

#include "Common/Assorted/Pimpl.h"
#include <utility>

//Default constructor.
template<typename Type>
Pimpl<Type>::Pimpl()
{
	this->data = new Type;
}

//Multi-argument constructor.
template<typename Type>
template<typename ...Args>
Pimpl<Type>::Pimpl(Args && ...args)
{
	this->data = new Type(std::forward<Args>(args)...);
}

//Copy constructor.
template<typename Type>
Pimpl<Type>::Pimpl(const Pimpl &other)
{
	this->data = new Type(*(other.data));
}

//Copy-assignment operator.
template<typename Type>
Pimpl<Type> &Pimpl<Type>::operator=(const Pimpl &other)
{
	if(this != &other)
	{
		Type *temp = new Type(*(other.data));
		std::swap(this->data, temp);
		delete temp;
	}
	
	return *this;
}

//Move constructor.
template<typename Type>
Pimpl<Type>::Pimpl(Pimpl &&other)
{
	//Swap the pointers so this class gets the old data pointer, and 'other' gets the null pointer.
	std::swap(this->data, other.data);
}

//Move-assignment operator.
template<typename Type>
Pimpl<Type> &Pimpl<Type>::operator=(Pimpl &&other)
{
	//Swap the pointers so this class gets the old data pointer, and 'other' gets the null pointer.
	std::swap(this->data, other.data);
	return *this;
}

//Destructor.
template<typename Type>
Pimpl<Type>::~Pimpl()
{
	delete this->data;
}

//Non-const access:
template<typename Type>
Type *Pimpl<Type>::operator->()
{
	return this->data;
}

template<typename Type>
Type *Pimpl<Type>::operator*()
{
	return this->data;
}

//Const access:
template<typename Type>
const Type *Pimpl<Type>::operator->() const
{
	return this->data;
}

template<typename Type>
const Type *Pimpl<Type>::operator*() const
{
	return this->data;
}

#endif //COMMON_ASSORTED_PIMPL_IMPLEMENTATION_H
The 'Pimpl' class is a reusable and consistent tool to Pimpl my classes. It manages the data pointer for you, so RAII, but at the same time is compact and small enough to save the (presumed, on my part) compile overhead of #including <memory> for unique_ptr, which (in GCC) includes a bunch of other headers that include other headers.


Pimpl_Impl

<offtopic> Pimpl Impl sounds like a Nordic pop band </offtopic>

I'd call Servant's production something along the lines of "massive overkill". Usually I find using an auto_ptr and dumping the entire struct in the cpp file to do just fine :)

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

... but some boost classes (one of them I'm using is unordered_map) have very difficult template declarations with other dependencies in it

unordered_map was put in the C++ standard as of C++11. Have you considered migrating to C++11?

Thanks all for your advice, I really appreciate it. :D

... but some boost classes (one of them I'm using is unordered_map) have very difficult template declarations with other dependencies in it

unordered_map was put in the C++ standard as of C++11. Have you considered migrating to C++11?

Yes, but I'm not doing it yet, but I will very very soon.

You can make low level wrapper classes to access things you want to hide and simplify/provide a few helper methods (that often happens automatically if you think a bit about designing the code in a way thats not a single godclass). Then you only forward declare these and have some pointers inside in your header files for your higher level public API class and you get that hiding with no need for explicit pimpl structs and have partitioned your code a bit better.

Or you could make a base class with virtual functions, hide the implementation including the weird things in derived classes and let the users only create these through a factory function.

This topic is closed to new replies.

Advertisement