Please have a look at directory structure first [ attached at the bottom of the question end].
Following are my Cmake files :
Main cmake
cmake_minimum_required(VERSION 3.9) set (CMAKE_CXX_STANDARD 14) add_executable (test main.cc) target_include_directories(test PUBLIC test_include_interface) target_link_libraries(test PUBLIC test_test)
Test cmake :
add_library(test_include_interface INTERFACE) target_include_directories(test_include_interface INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) add_library(test_test STATIC test_interface.h test.h test.cc) target_include_directories(test_test INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
test.h
#pragma once
#include "test_interface.h"
#include <memory>
class Test : public ITest {
public:
Test();
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
test.cc
#include "test.h"
class Test::Impl {
public:
Impl() {}
~Impl() {}
};
Test::Test() : impl_{std::make_unique<Impl>()} {}
Error :
In file included from /usr/include/c /9/memory:80,
from /home/vkd0726/test/test/test.h:9,
from /home/vkd0726/test/main.cc:2:
/usr/include/c /9/bits/unique_ptr.h: In instantiation of ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Test::Impl]’:
/usr/include/c /9/bits/unique_ptr.h:292:17: required from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Test::Impl; _Dp = std::default_delete<Test::Impl>]’
test/test/test.h:11:7: required from here
/usr/include/c /9/bits/unique_ptr.h:79:16: error: invalid application of ‘sizeof’ to incomplete type ‘Test::Impl’
79 | static_assert(sizeof(_Tp)>0,
| ^~~~~~~~~~~
make[2]: *** [CMakeFiles/test.dir/build.make:63: CMakeFiles/test.dir/main.cc.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:76: CMakeFiles/test.dir/all] Error 2
make: *** [Makefile:84: all] Error 2
Please help me understand this error and how to resolve it ? Is it a problem with the Cmake or code itself. I have seen such "Impl" design pattern use at many places but unable to write a test snippet. What is wrong here ?
Directory structure
CodePudding user response:
The Test
does not contain a user defined destructor, so the compiler generates a inlined version of this destructor. Since this destructor involves logic to delete the Test::Impl
object when the destructor of std::unique_ptr<Test::Impl>
is invoked, you get the error your compiler complains about.
You can fix this by defining the destructor in test.cc
:
test.h
#include <memory>
class Test : public ITest {
public:
Test();
~Test(); // new
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
test.cc
class Test::Impl {
...
};
...
Test::~Test() = default;
CodePudding user response:
This is a common problem faced with std::unique_ptr
for pimpl. The primary approach would be - as @fabian mentioned - to provide a declaration fir the nesting class's destructor and define it in the same translation unit as the pimpl implementation (most probably as a defaulted function). But this approach might disable some corner case optimizations, due to compiler not being able to deduce the triviality of the destructor of nesting class(Test
according to OP). Another approach would be to provide a deleter class for the std::unique_ptr
and leave the nesting class`s destructor intact:
class test{
public:
test();
private:
class impl;
struct deleter{
void operator(impl *)const;
};
std::unique_ptr<impl, deleter> up_impl;
};
Now define the deleter's function call operator in the same translation unit as impl to:
void test::deleter::operator(test::impl * ptr) const
{ delete ptr; };
This looks a little more work. But it pays off with an implicitly defined destructor. It also better suits the rule of 0/3/5.