In rare cases it can be useful to violate ODR.
For example, you can instead of std::unique_ptr<MyPimplType>
use std::aligned_storage<MyPimplType_sizeof, MyPimplType_alignof>
and test the real sizeof and alignment in the constructor/destructor of MyPimplType
class. This called aligned storage pimpl
or something similar. Useful when you want to replace impl-by-pointer (by smart pointer to impl) by impl-by-value (aligned storage instead of impl).
Next, you can create new type of aligned storage, which can test the sizeof and the alignment automatically in it's constructor/destructor:
private/my_aligned_storage_by_decl.hpp or public/my_aligned_storage_by_decl.hpp
template <typename incomplete_type_to_align, size_t sizeof_, size_t alignof_>
class my_aligned_storage_by { ... };
private/my_aligned_storage_by_impl.hpp
// incomplete_type_to_align must be already complete here!
template <typename incomplete_type_to_align, size_t sizeof_, size_t alignof_>
my_aligned_storage_by<...>::my_aligned_storage_by(...) {
static_assert(sizeof_ == sizeof(incomplete_type_to_align), ...);
static_assert(alignof_ == std::alignment_of<incomplete_type_to_align>::value, ...);
}
This can be reached only through the ODR violation AND if public and private headers can not be merged to a single header, where the public and private headers having 2 different definitions of the same my_aligned_storage_by
class.
The implementation:
https://github.com/andry81/tacklelib/tree/HEAD/include/tacklelib/tackle/aligned_storage/
Example of usage:
include/myheader.hpp
#include <tacklelib/tackle/aligned_storage/public/aligned_storage_by_decl.hpp>
#define MYCLASS_SIZEOF ...
#define MYCLASS_ALIGNOF ...
class MyClass
{
// public methods...
MyClass(...);
// replacement as impl-by-value:
struct This;
tackle::aligned_storage_by<This,
MYCLASS_SIZEOF,
MYCLASS_ALIGNOF,
tackle::tag_pttn_control_lifetime> m_this;
};
void myfoo(const MyClass & myboo);
src/_impl/myheader_this.hpp
#include <myheader.hpp>
struct MyClass::This
{
// data members and private function of class MyClass is placed here...
};
src/MyClass.cpp
#include <tacklelib/tackle/aligned_storage/private/aligned_storage_by_decl.hpp>
#include <src/_impl/MyClass_this.hpp>
#include <tacklelib/tackle/aligned_storage/private/aligned_storage_by_impl.hpp>
// public methods implementation...
MyClass::MyClass()
{
m_this.construct_default();
}
MyClass::MyClass(...)
{
m_this.construct(This{...});
}
void myfoo(const MyClass & myboo)
{
auto & realboo = *myboo.m_this.this_();
// deal with realboo...
}
Here is some major drawbacks of such approach:
You have to split headers containing classes with my_aligned_storage
as members into public/private headers too and leave public headers for SDK, but include private one into cpp files instead of public. (*)
You have explicitly control the order of include of such headers, because private header can be silently included instead of the public (but not vice versa).
You have to include implementation with sizeof/alignment test asserts only when the type become fully complete, which is sometimes is not always possible.
You have explicitly specify sizeof and alignment, which can be different in different contexts, for example, debug/release, windows/linux, msvc/gcc and so on.
(*) In case if public and private header of the my_aligned_storage
can not be merged into single public declaration header.
These drawbacks can be avoided or ignored in cases, where an aligned user class is really not big and frequently constructed/assigned/copied, like a builtin type.