STL vector and unique_ptr with custom deleter classes - c++

I have some c++ code that compiles with clang 3.2-7 and gcc 4.8.1 but not with gcc 4.6.3. I'm compiling with c++0x.
I'm implementing a 'ResourcePool' that manages a fixed set of resources. It allocates resources out by providing a unique_ptr with a custom deleter class; when the object requesting the resource drops out of scope, the deleter returns the resource to a pool.
The deleter class looks like so:
template <typename T>
class ResourcePoolManager {
public:
ResourcePoolManager(ResourcePool<T> & pool)
: pool(pool)
{
}
~ResourcePoolManager() {};
void operator()(T* releasedResource) const {
pool.releaseResource(releasedResource);
}
private:
ResourcePool<T> & pool;
};
The pool itself looks like (edited unrelated methods out):
template <typename T>
class ResourcePool {
public:
ResourcePool()
: manager(*this)
{
}
std::unique_ptr<T, ResourcePoolManager<T>> requestResource() {
if(availableResources.size() == 0) {
return std::unique_ptr<T, ResourcePoolManager<T>>(NULL, manager);
} else {
T * resource = availableResources.front();
availableResources.pop_front();
return std::unique_ptr<T, ResourcePoolManager<T>>(resource, manager);
}
}
private:
friend class ResourcePoolManager<T>;
void releaseResource(T * releasedResource) {
availableResources.push_back(releasedResource);
}
ResourcePoolManager<T> manager;
std::deque<T *> availableResources;
std::deque<T *> allResources;
};
The problem occurs when I try to pull resources out of the pool and store them in a std::vector; where OpenCLDevice is the resource in question:
ResourcePool<OpenCLDevice> computeDevicePool;
std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice>> devicePtr = computeDevicePool.requestResource();
std::vector<std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice>>> gpus;
gpus.push_back(std::move(devicePtr));
As mentioned up top, clang and a newer version are gcc are fine with this. However gcc 4.6.3 is coming out with:
/usr/include/c++/4.6/bits/unique_ptr.h: In member function 'std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(std::unique_ptr<_Tp, _Dp>&&) [with _Tp = OpenCLDevice, _Dp = ResourcePoolManager<OpenCLDevice>, std::unique_ptr<_Tp, _Dp> = std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> >]':
/usr/include/c++/4.6/bits/vector.tcc:319:4: instantiated from 'void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> >}, _Tp = std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> >, _Alloc = std::allocator<std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> > >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> >*, std::vector<std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> > > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> >*]'
/usr/include/c++/4.6/bits/vector.tcc:102:4: instantiated from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> >}, _Tp = std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> >, _Alloc = std::allocator<std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> > >]'
/usr/include/c++/4.6/bits/stl_vector.h:840:9: instantiated from 'void std::vector<_Tp, _Alloc>::push_back(std::vector<_Tp, _Alloc>::value_type&&) [with _Tp = std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> >, _Alloc = std::allocator<std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> > >, std::vector<_Tp, _Alloc>::value_type = std::unique_ptr<OpenCLDevice, ResourcePoolManager<OpenCLDevice> >]'
trunk/src/dpa/distinguishers/GenericOpenCLDPA.cpp:109:39: instantiated from here
/usr/include/c++/4.6/bits/unique_ptr.h:176:2: error: use of deleted function 'ResourcePoolManager<OpenCLDevice>& ResourcePoolManager<OpenCLDevice>::operator=(const ResourcePoolManager<OpenCLDevice>&)'
ResourcePool.hpp:30:7 error: 'ResourcePoolManager<OpenCLDevice> & ResourcePoolManager<OpenCLDevice>::operator=(const ResourcePoolManager<OpenCLDevice>&)'
is implicitly deleted because the default definition would be ill-formed:
ResourcePool.hpp:30:7: error: non-static reference member 'ResourcePool<OpenCLDevice>& ResourcePoolManager<OpenCLDevice>::pool', can't use default assignment operator
The compiler stops at the final line where the pointer is moved into the gpus vector, indicating to me that something is being copied when it shouldn't be. Line 30 is the definition of the ResourcePoolManager class class ResourcePoolManager {.
Am I doing something incorrectly with the management of the custom deleter where the newer compilers are letting me get away with things?

Looks like a bug in the libstdc++ 4.6 unique_ptr that expects the deleter type to be copyable. Changing ResourcePoolManager::pool to a pointer instead of a reference is an effective workaround.
More specifically, this program reproduces the bug:
#include <memory>
#include <vector>
struct deleter {
deleter() : c(*"") {}
void operator () (int *) const {}
deleter(deleter&&) = default;
deleter& operator = (deleter&&) = default;
const char& c;
};
int main() {
std::vector<std::unique_ptr<int, deleter>> vec;
vec.push_back(std::unique_ptr<int, deleter>{});
}
simply declaring the vector is fine, its the instantiation of push_back that fails.
Clarification: the issue seems to be that the 4.6 implementation of vector is using = to move assign elements when it resizes, whereas the 4.8 implementation is exclusively using move construction. A class with a reference member - such as your deleter - is move constructible, but not move (or copy, for that matter) assignable. You can see the same error message in 4.8 if you assign to the vector:
int main() {
std::vector<std::unique_ptr<int, deleter>> vec;
vec.push_back(std::unique_ptr<int, deleter>{new int(42)});
vec[0] = std::unique_ptr<int, deleter>{new int(42)};
}
std::vector::push_back is specified to require only that T be MoveInsertable, so I believe this is certainly a bug in libstdc++ 4.6.

Related

How do I move a vector of objects that contain a unique_ptr as a member?

I have a function that returns an std::vector of a class that contains a std::unique_ptr as member. I need to store this vector object on the heap so I can pass it through a C-style DLL interface.
See the following code sample:
#include <iostream>
#include <cstdlib>
#include <memory>
#include <vector>
// I have control over the following two classes
class SomeBigClassWithManyMembers { };
class MyClass
{
std::unique_ptr<SomeBigClassWithManyMembers> up;
public:
static const std::vector<MyClass> GetMany()
{
// imagine this does lots of work
return std::vector<MyClass>(50);
}
// following code is suggested in https://stackoverflow.com/questions/31430533/move-assignable-class-containing-vectorunique-ptrt but doesn't help
/*
MyClass() { }
MyClass(MyClass&& other): up{std::move(other.up)} { }
MyClass& operator=(MyClass&& other)
{
up = std::move(other.up);
return *this;
}
*/
};
// Imagine that I can't change this C-style code - it's a fixed interface provided by someone else
struct NastyCStyleStruct
{
void* v;
};
void NastyCStyleInterface(NastyCStyleStruct s) { printf("%u", (unsigned int)((std::vector<MyClass>*)s.v)->size()); }
int main()
{
NastyCStyleStruct s;
s.v = new std::vector<MyClass>(std::move(MyClass::GetMany()));
NastyCStyleInterface(s);
return 0;
}
Note that in my actual code, the vector needs to outlive the function in which it is created (because this is being done in a DLL), so writing
auto vec = MyClass::GetMany();
s.v = &vec;
would not suffice. The vector must be stored on the heap.
The trouble here is that the code seems to try to use the (non-existent) copy constructor of MyClass. I can't understand why the copy constructor is being invoked, because I am explicitly asking for move semantics with std::move. Not even initialising s.v to a fresh new std::vector of the appropriate size and calling the three-argument version of std::move works.
Errors from g++:
In file included from /usr/include/c++/7/memory:64:0,
from stackq.cpp:4:
/usr/include/c++/7/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = MyClass; _Args = {const MyClass&}]’:
/usr/include/c++/7/bits/stl_uninitialized.h:83:18: required from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const MyClass*, std::vector<MyClass> >; _ForwardIterator = MyClass*; bool _TrivialValueTypes = false]’
/usr/include/c++/7/bits/stl_uninitialized.h:134:15: required from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const MyClass*, std::vector<MyClass> >; _ForwardIterator = MyClass*]’
/usr/include/c++/7/bits/stl_uninitialized.h:289:37: required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const MyClass*, std::vector<MyClass> >; _ForwardIterator = MyClass*; _Tp = MyClass]’
/usr/include/c++/7/bits/stl_vector.h:331:31: required from ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = MyClass; _Alloc = std::allocator<MyClass>]’
stackq.cpp:43:65: required from here
/usr/include/c++/7/bits/stl_construct.h:75:7: error: use of deleted function ‘MyClass::MyClass(const MyClass&)’
{ ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
stackq.cpp:10:7: note: ‘MyClass::MyClass(const MyClass&)’ is implicitly deleted because the default definition would be ill-formed:
class MyClass
^~~~~~~
stackq.cpp:10:7: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = SomeBigClassWithManyMembers; _Dp = std::default_delete<SomeBigClassWithManyMembers>]’
In file included from /usr/include/c++/7/memory:80:0,
from stackq.cpp:4:
/usr/include/c++/7/bits/unique_ptr.h:388:7: note: declared here
unique_ptr(const unique_ptr&) = delete;
^~~~~~~~~~
How can I repair this code so that the vector object itself is stored on the heap?
The problem here is that GetMany is defined to return a const std::vector. Even though it's returning by value, so there's no risk of unwanted side-effects if you mutate the result, the compiler still enforces the type (auto just copied the exact return type of the function after all), making it impossible to move from it. So instead of cheaply copying a few pointer/size_t-sized values (to the vector contents, size and capacity), it had to do a full copy construction, including copy constructing all the values stored in the original vector. That fails due to the unique_ptr member in the stored instances.
Simply dropping the const from the return type should allow std::move/vector's move constructor to do their job, allowing the ultra-cheap extraction of the contents of the vector returned by GetMany with no copies (or even moves) of any instance of MyClass.

C++ External Class in std::vector ( definition would be ill-formed )

I have built a class in an external library that I was hoping to use in other areas. However, when I try to use the class within an std::vector I get compile errors
from ../../src/geom/geom.cpp:21:
/usr/include/c++/4.8.2/ext/new_allocator.h: In instantiation of `void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = GenUtils::MeshNormalData; _Args = {const GenUtils::MeshNormalData&}; _Tp = GenUtils::MeshNormalData]`:
/usr/include/c++/4.8.2/bits/alloc_traits.h:254:4: required from `static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = GenUtils::MeshNormalData; _Args = {const GenUtils::MeshNormalData&}; _Alloc = std::allocator<GenUtils::MeshNormalData>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]`
/usr/include/c++/4.8.2/bits/alloc_traits.h:393:57: required from `static decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = GenUtils::MeshNormalData; _Args = {const GenUtils::MeshNormalData&}; _Alloc = std::allocator<GenUtils::MeshNormalData>; decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) = <type error>]`
/usr/include/c++/4.8.2/bits/stl_vector.h:906:34: required from `void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = GenUtils::MeshNormalData; _Alloc = std::allocator<GenUtils::MeshNormalData>; std::vector<_Tp, _Alloc>::value_type = GenUtils::MeshNormalData]’
../../src/geom/geom.cpp:315:47: required from here
/usr/include/c++/4.8.2/ext/new_allocator.h:120:4: error: use of deleted function `GenUtils::MeshNormalData::MeshNormalData(const GenUtils::MeshNormalData&)`
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
^
In file included from ../../src/geom/geom.h:60:0,
from ../../src/geom/geom.cpp:21:
../../src/libs/utils/generalUtils.h:94:7: note: `GenUtils::MeshNormalData::MeshNormalData(const GenUtils::MeshNormalData&)` is implicitly deleted because the default definition would be ill-formed:
class MeshNormalData
in libs/generalUtils.h
namespace GenUtils
{
class MeshNormalData
{
public:
MeshNormalData(){}
};
}
in geom.h
#include generalUtils.h
class TestGeo : public Base
{
public:
TestGeo(){};
private:
std::vector< GenUtils::MeshNormalData > meshnormals;
void compute_geo();
};
in geom.cpp
void TestGeo::compute_geo()
{
meshnormals.clear()
GenUtils::MeshNormalData normals()
//- doing other computation on normals
meshnormals.push_back( normals );
}
What am I missing? Any advice? Need help deciphering my error.
The key part of the error is where it says the definition of MeshNormalData's copy constructor is deleted.
The error will be generated because something about the program requires the copy constructor to not be deleted: to be accessible and usable.
The copy is needed here:
meshnormals.push_back( normals );
because you do not write std::move.
You can get away with a non-copyable class in a vector if it's moveable. But you may wish to make your class copyable. Presumably in your real code there's something about the type that prevents that.
You need to provide the copy constructor for the class.
There is no problem with Vector as you mentioned in question title, its class MeshNormalData.
Push_back expect the copy constructor.
Below link can help you more:
What are all the member-functions created by compiler for a class? Does that happen all the time?
Added a copy constructor to make the it copyable.
namespace GenUtils
{
class MeshNormalData
{
public:
MeshNormalData(){}
MeshNormalData( const MeshNormalData &obj){}; // copy
};
}
using meshnormals.push_back(std::move(normals)); worked too.
Thanks Evg!

std::vector of class with private constructor does not compile when using modern C++

The code I'm working on was initially designed using C++03, and compiles and functions without errors using g++ -std=c++03. My goal is to have the same code compile using g++ -std=c++17.
The code contains a MyClass which contains a NestedClass. Only MyClassshould be able to use, create, and modify instances of NestedClass, which are stored in a std::vector< NestedClass >. As such NestedClass contains a private constructor, and declares MyClass and std::vector< NestedClass > as friends.
Minimal Example:
#include <vector>
class MyClass {
public:
class NestedClass {
friend class MyClass;
friend class std::vector< NestedClass >;
double _d;
NestedClass( double d = 0.0 ) : _d(d){ }
};
private:
std::vector< NestedClass > data;
public:
MyClass(){
data.resize( 40 );
}
};
int main(){
MyClass myclass = MyClass();
return 0;
}
This minimal example fails when compiling -std=c++17 with the following error:
/usr/include/c++/7/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = MyClass::NestedClass; _Args = {}]’:
/usr/include/c++/7/bits/stl_uninitialized.h:527:18: required from ‘static _ForwardIterator std::__uninitialized_default_n_1<_TrivialValueType>::__uninit_default_n(_ForwardIterator, _Size) [with _ForwardIterator = MyClass::NestedClass*; _Size = long unsigned int; bool _TrivialValueType = false]’
/usr/include/c++/7/bits/stl_uninitialized.h:583:20: required from ‘_ForwardIterator std::__uninitialized_default_n(_ForwardIterator, _Size) [with _ForwardIterator = MyClass::NestedClass*; _Size = long unsigned int]’
/usr/include/c++/7/bits/stl_uninitialized.h:645:44: required from ‘_ForwardIterator std::__uninitialized_default_n_a(_ForwardIterator, _Size, std::allocator<_Tp>&) [with _ForwardIterator = MyClass::NestedClass*; _Size = long unsigned int; _Tp = MyClass::NestedClass]’
/usr/include/c++/7/bits/vector.tcc:563:35: required from ‘void std::vector<_Tp, _Alloc>::_M_default_append(std::vector<_Tp, _Alloc>::size_type) [with _Tp = MyClass::NestedClass; _Alloc = std::allocator<MyClass::NestedClass>; std::vector<_Tp, _Alloc>::size_type = long unsigned int]’
/usr/include/c++/7/bits/stl_vector.h:692:21: required from ‘void std::vector<_Tp, _Alloc>::resize(std::vector<_Tp, _Alloc>::size_type) [with _Tp = MyClass::NestedClass; _Alloc = std::allocator<MyClass::NestedClass>; std::vector<_Tp, _Alloc>::size_type = long unsigned int]’
bug.cpp:21:20: required from here
/usr/include/c++/7/bits/stl_construct.h:75:7: error: ‘MyClass::NestedClass::NestedClass(double)’ is private within this context
{ ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bug.cpp:11:4: note: declared private here
NestedClass( double d = 0.0 ) : _d(d){ }
How can I rewrite the code so that it compiles using c++17?
Changing std::vector< NestedClass > to std::vector< NestedClass * > is not an option since it would require rewriting code that uses MyClass which I do not control.
The minimalistic fix for your particular example is
data.resize( 40, {} );
Call the private constructor yourself, so that vector only needs to call the (implicitly declared) public ones.
In general, befriending something in a library you don't control doesn't work. You have no idea whether said something is actually going to delegate the work to something else.
In vector's case, it is pretty much required to delegate said work to something else.
A proper fix will likely involve changes to the classes involved. One possibility is the passkey idiom: make the constructors public, but only callable with an argument of a private type.

How to fix vector of objects that have unique_ptr

I have following code that has vector of a class that has some member declared as unique_ptr.
struct Container
{
struct Nested{
std::unique_ptr<Container> node;
Nested(std::unique_ptr<Container> t) : node(std::move(t)) {}
Nested(const Nested& t) { node = std::move(t.node); };
};
std::vector<Nested> edges;
};
typedef std::unique_ptr<Container> UCont;
typedef Container::Nested Nested;
int main()
{
std::unique_ptr<Container> object = UCont(new Container{{
Nested(UCont(new Container{{}})),
Nested(UCont(new Container{{}})),
Nested(UCont(new Container{{}}))
}});
}
Now compiling this is giving the following error:
..\00.UniquePtrVector.cpp: In copy constructor 'Container::Nested::Nested(const Container::Nested&)':
..\00.UniquePtrVector.cpp:20:35: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Container; _Dp = std::default_delete<Container>]'
Nested(const Nested& t) { node = std::move(t.node); };
^
In file included from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\locale_conv.h:41:0,
from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\locale:43,
from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\iomanip:43,
from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\mingw32\bits\stdc++.h:71,
from ..\00.UniquePtrVector.cpp:10:
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\unique_ptr.h:357:19: note: declared here
unique_ptr& operator=(const unique_ptr&) = delete;
I am not sure how to fix this error. I guess removing the copy constructor is not an option either. Any help?
EDIT:
Changing to
Nested(std::unique_ptr<Container>&& t) : node(std::move(t)) {}
Nested(Nested&& t) : node(std::move(t.node)) {}
Nested(const Nested& t) =delete;
is also giving error:
In file included from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_tempbuf.h:60:0,
from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_algo.h:62,
from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\algorithm:62,
from c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\mingw32\bits\stdc++.h:64,
from ..\00.UniquePtrVector.cpp:10:
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_construct.h: In instantiation of 'void std::_Construct(_T1*, _Args&& ...) [with _T1 = Container::Nested; _Args = {const Container::Nested&}]':
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_uninitialized.h:75:18: required from 'static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const Container::Nested*; _ForwardIterator = Container::Nested*; bool _TrivialValueTypes = false]'
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_uninitialized.h:126:15: required from '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const Container::Nested*; _ForwardIterator = Container::Nested*]'
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_uninitialized.h:281:37: required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = const Container::Nested*; _ForwardIterator = Container::Nested*; _Tp = Container::Nested]'
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_vector.h:1290:33: required from 'void std::vector<_Tp, _Alloc>::_M_range_initialize(_ForwardIterator, _ForwardIterator, std::forward_iterator_tag) [with _ForwardIterator = const Container::Nested*; _Tp = Container::Nested; _Alloc = std::allocator<Container::Nested>]'
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_vector.h:377:21: required from 'std::vector<_Tp, _Alloc>::vector(std::initializer_list<_Tp>, const allocator_type&) [with _Tp = Container::Nested; _Alloc = std::allocator<Container::Nested>; std::vector<_Tp, _Alloc>::allocator_type = std::allocator<Container::Nested>]'
..\00.UniquePtrVector.cpp:36:11: required from here
c:\mingw\lib\gcc\mingw32\5.3.0\include\c++\bits\stl_construct.h:75:7: error: use of deleted function 'Container::Nested::Nested(const Container::Nested&)'
{ ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
^
..\00.UniquePtrVector.cpp:20:11: note: declared here
Nested(const Nested& t) =delete;
^
When you move t.node in your copy constructor, t.node needs to change. But t is const, so the move is invalid. unique_ptr cannot be copy constructed, thus struct Nested cannot be copy constructed either.
To make it work, you'll need to supply a move constructor and delete the copy constructor. Something like this:
struct Nested{
std::unique_ptr<Container> node;
Nested(std::unique_ptr<Container>&& t) : node(std::move(t)) {}
Nested(Nested&& t) : node(std::move(t.node)) {}
Nested(const Nested& t) =delete;
};
tl;dr : Consider using the rule of zero
I would be wary of explicitly specifying all those constructors in your class. The fact that unique_ptr's can't be copied will get you in all sorts of trouble when you do things the way you tried to. Here's what happens when, instead, you don't specify any constructors:
#include <vector>
#include <memory>
struct Container
{
struct Nested{
std::unique_ptr<Container> node;
//
// Nested(std::unique_ptr<Container> t) : node(std::move(t)) {}
// Nested(const Nested& t) { node = std::move(t.node); };
};
std::vector<Nested> edges;
};
typedef std::unique_ptr<Container> UCont;
typedef Container::Nested Nested;
int main()
{
auto c1 = new Container{};
auto c2 = new Container{};
auto c3 = new Container{};
std::unique_ptr<Container> u1 {c1};
std::unique_ptr<Container> u2 {c2};
std::unique_ptr<Container> u3 {c3};
Nested n1 {std::move(u1)};
Nested n2 {std::move(u2)};
Nested n3 {std::move(u3)};
auto v = std::vector<Nested>{3};
v.push_back(std::move(n1));
v.push_back(std::move(n2));
v.push_back(std::move(n3));
auto c5 = new Container { std::move(v) };
std::unique_ptr<Container> object = UCont(std::move(c5));
}
I've broken everything down to shorter statements for clarity (mostly).
The root cause of the error I was getting is due to use of unique_ptr inside the object that is used in intializer list. Similar to vector of simple unique_ptr (as in here Initializing container of unique_ptrs from initializer list fails with GCC 4.7 ) an object that also has unique_ptr in its data model can also NOT be used in initializer list.
Similar to the other link, cause is same; initializer list always performs copies and unique_ptr cannot be copied. So we have to use emplace_back/push_back.
So even with constructor in place the following solution works
struct Container
{
struct Nested{
std::unique_ptr<Container> node;
Nested(): node(nullptr) {}
Nested(std::unique_ptr<Container> t) : node(std::move(t)) {}
};
std::vector<Nested> edges;
};
typedef std::unique_ptr<Container> UCont;
typedef Container::Nested Nested;
int main()
{
auto v = std::vector<Nested>{3};
v.push_back(std::move(Nested(std::move(std::unique_ptr<Container>(new Container{})))));
v.push_back(std::move(Nested(std::move(std::unique_ptr<Container>(new Container{})))));
v.push_back(std::move(Nested(std::move(std::unique_ptr<Container>(new Container{})))));
std::unique_ptr<Container> object = UCont(new Container { std::move(v) });
}

Is there a good way to implement a template interface in C++?

I've been looking around for a way to do this, and I'm not sure it's even possible. I've got a class in Java that takes an instance of a generically-typed interface as part of its constructor, and I'd like to recreate it in C++ (it's a utility class that is handy in many situations). To the best of my understanding, the closest equivalent to an interface in C++ is a pure virtual class, and the (somewhat) equivalent of generics is templates.
So let's say I have some classes defined as follows:
template<typename R>
class AnInterface
{
public:
virtual R run() = 0;
virtual ~AnInterface() {}
};
template<typename R>
class Processor
{
public:
Processor(std::vector<AnInterface<R>> toRun) : toRun(toRun) {}
std::vector<R> process() {
std::vector<R> res;
for(int i = 0; i < this->toRun.size(); ++i)
res.push_back(toRun[i].run());
return res;
}
private:
std::vector<AnInterface<R>> toRun;
};
class AnInstanceClass : public AnInterface<int>
{
int run() { return 1+1; }
};
I'd like to be able to do something like this with them:
int main()
{
std::vector<AnInterface<int>> toRun;
toRun.push_back(AnInstanceClass());
toRun.push_back(AnInstanceClass());
Processor<int> p(toRun);
std::vector<int> p.process();
}
Basically, have a class who's job is to take a list of objects, run them, and then return a list of their results, while being agnostic to the types of objects and results (assuming that the objects have a 'run' function). In Java, I accomplished this with generics and interfaces. I tried implementing the above solution in C++, but it doesn't compile and the compiler output is very cryptic, suggesting that I'm screwing up something very fundamental to the language. My C++ is a little rusty, so I'm not exactly sure what that is. How can something like this be implemented in C++?
Edit: Here's the error message when I try to compile the above code:
In file included from /usr/include/c++/4.8/vector:64:0,
from test.cpp:1:
/usr/include/c++/4.8/bits/stl_vector.h: In instantiation of ‘class std::vector<AnInterface<int> >’:
test.cpp:36:36: required from here
/usr/include/c++/4.8/bits/stl_vector.h:704:7: error: cannot allocate an object of abstract type ‘AnInterface<int>’
resize(size_type __new_size, value_type __x = value_type())
^
test.cpp:4:7: note: because the following virtual functions are pure within ‘AnInterface<int>’:
class AnInterface
^
test.cpp:7:19: note: R AnInterface<R>::run() [with R = int]
virtual R run() = 0;
^
test.cpp: In function ‘int main()’:
test.cpp:40:23: error: expected initializer before ‘.’ token
std::vector<int> p.process();
^
test.cpp: In instantiation of ‘Processor<R>::Processor(std::vector<AnInterface<R> >) [with R = int]’:
test.cpp:39:27: required from here
test.cpp:15:68: error: no matching function for call to ‘std::vector<int, std::allocator<int> >::vector(std::vector<AnInterface<int> >&)’
Processor(std::vector<AnInterface<R> > toRun) : toRun(toRun) {}
^
test.cpp:15:68: note: candidates are:
In file included from /usr/include/c++/4.8/vector:64:0,
from test.cpp:1:
/usr/include/c++/4.8/bits/stl_vector.h:398:9: note: template<class _InputIterator> std::vector<_Tp, _Alloc>::vector(_InputIterator, _InputIterator, const allocator_type&)
vector(_InputIterator __first, _InputIterator __last,
^
/usr/include/c++/4.8/bits/stl_vector.h:398:9: note: template argument deduction/substitution failed:
test.cpp:15:68: note: candidate expects 3 arguments, 1 provided
Processor(std::vector<AnInterface<R> > toRun) : toRun(toRun) {}
^
In file included from /usr/include/c++/4.8/vector:64:0,
from test.cpp:1:
/usr/include/c++/4.8/bits/stl_vector.h:310:7: note: std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = int; _Alloc = std::allocator<int>]
vector(const vector& __x)
^
/usr/include/c++/4.8/bits/stl_vector.h:310:7: note: no known conversion for argument 1 from ‘std::vector<AnInterface<int> >’ to ‘const std::vector<int, std::allocator<int> >&’
/usr/include/c++/4.8/bits/stl_vector.h:295:7: note: std::vector<_Tp, _Alloc>::vector(std::vector<_Tp, _Alloc>::size_type, const value_type&, const allocator_type&) [with _Tp = int; _Alloc = std::allocator<int>; std::vector<_Tp, _Alloc>::size_type = long unsigned int; std::vector<_Tp, _Alloc>::value_type = int; std::vector<_Tp, _Alloc>::allocator_type = std::allocator<int>]
vector(size_type __n, const value_type& __value = value_type(),
^
/usr/include/c++/4.8/bits/stl_vector.h:295:7: note: no known conversion for argument 1 from ‘std::vector<AnInterface<int> >’ to ‘std::vector<int, std::allocator<int> >::size_type {aka long unsigned int}’
/usr/include/c++/4.8/bits/stl_vector.h:256:7: note: std::vector<_Tp, _Alloc>::vector(const allocator_type&) [with _Tp = int; _Alloc = std::allocator<int>; std::vector<_Tp, _Alloc>::allocator_type = std::allocator<int>]
vector(const allocator_type& __a)
^
/usr/include/c++/4.8/bits/stl_vector.h:256:7: note: no known conversion for argument 1 from ‘std::vector<AnInterface<int> >’ to ‘const allocator_type& {aka const std::allocator<int>&}’
/usr/include/c++/4.8/bits/stl_vector.h:248:7: note: std::vector<_Tp, _Alloc>::vector() [with _Tp = int; _Alloc = std::allocator<int>]
vector()
^
/usr/include/c++/4.8/bits/stl_vector.h:248:7: note: candidate expects 0 arguments, 1 provided
In file included from /usr/include/c++/4.8/vector:69:0,
from test.cpp:1:
/usr/include/c++/4.8/bits/vector.tcc: In instantiation of ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, const _Tp&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<AnInterface<int>*, std::vector<AnInterface<int> > >; typename std::_Vector_base<_Tp, _Alloc>::pointer = AnInterface<int>*]’:
/usr/include/c++/4.8/bits/stl_vector.h:913:28: required from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; std::vector<_Tp, _Alloc>::value_type = AnInterface<int>]’
test.cpp:37:38: required from here
/usr/include/c++/4.8/bits/vector.tcc:329:19: error: cannot allocate an object of abstract type ‘AnInterface<int>’
_Tp __x_copy = __x;
^
test.cpp:4:7: note: since type ‘AnInterface<int>’ has pure virtual functions
class AnInterface
^
In file included from /usr/include/c++/4.8/vector:69:0,
from test.cpp:1:
/usr/include/c++/4.8/bits/vector.tcc:329:8: error: cannot declare variable ‘__x_copy’ to be of abstract type ‘AnInterface<int>’
_Tp __x_copy = __x;
^
test.cpp:4:7: note: since type ‘AnInterface<int>’ has pure virtual functions
class AnInterface
^
In file included from /usr/include/x86_64-linux-gnu/c++/4.8/bits/c++allocator.h:33:0,
from /usr/include/c++/4.8/bits/allocator.h:46,
from /usr/include/c++/4.8/vector:61,
from test.cpp:1:
/usr/include/c++/4.8/ext/new_allocator.h: In instantiation of ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, const _Tp&) [with _Tp = AnInterface<int>; __gnu_cxx::new_allocator<_Tp>::pointer = AnInterface<int>*]’:
/usr/include/c++/4.8/ext/alloc_traits.h:216:9: required from ‘static void __gnu_cxx::__alloc_traits<_Alloc>::construct(_Alloc&, __gnu_cxx::__alloc_traits<_Alloc>::pointer, const _Tp&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; __gnu_cxx::__alloc_traits<_Alloc>::pointer = AnInterface<int>*]’
/usr/include/c++/4.8/bits/stl_vector.h:906:34: required from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; std::vector<_Tp, _Alloc>::value_type = AnInterface<int>]’
test.cpp:37:38: required from here
/usr/include/c++/4.8/ext/new_allocator.h:130:9: error: cannot allocate an object of abstract type ‘AnInterface<int>’
{ ::new((void *)__p) _Tp(__val); }
^
test.cpp:4:7: note: since type ‘AnInterface<int>’ has pure virtual functions
class AnInterface
^
In file included from /usr/include/c++/4.8/vector:62:0,
from test.cpp:1:
/usr/include/c++/4.8/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, const _T2&) [with _T1 = AnInterface<int>; _T2 = AnInterface<int>]’:
/usr/include/c++/4.8/bits/stl_uninitialized.h:75:53: required from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const AnInterface<int>*, std::vector<AnInterface<int> > >; _ForwardIterator = AnInterface<int>*; bool _TrivialValueTypes = false]’
/usr/include/c++/4.8/bits/stl_uninitialized.h:117:41: required from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const AnInterface<int>*, std::vector<AnInterface<int> > >; _ForwardIterator = AnInterface<int>*]’
/usr/include/c++/4.8/bits/stl_uninitialized.h:258:63: required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const AnInterface<int>*, std::vector<AnInterface<int> > >; _ForwardIterator = AnInterface<int>*; _Tp = AnInterface<int>]’
/usr/include/c++/4.8/bits/stl_vector.h:316:32: required from ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >]’
test.cpp:39:27: required from here
/usr/include/c++/4.8/bits/stl_construct.h:83:7: error: cannot allocate an object of abstract type ‘AnInterface<int>’
::new(static_cast<void*>(__p)) _T1(__value);
^
test.cpp:4:7: note: since type ‘AnInterface<int>’ has pure virtual functions
class AnInterface
You're basically (attempting to) re-create the functionality of std::generate. The difference is that generate doesn't rely on the somewhat clunky convention of a member function named run. Rather, it invokes something like a function (though it may, and often will, be an overloaded operator()).
We can also (frequently) avoid the separate definition of what you've named AnInstanceClass by defining the class in a lambda expression.
So, in this case, we'd be looking at something like:
std::vector<int> p;
std::generate_n(std::back_inserter(p), 2, [] { return 1 + 1; });
This is basically threading-agnostic, so if you want to run the individual tasks in separate threads, you can do that pretty easily as well. There are some caveats with std::async, but they're pretty much the same regardless of whether you involve std::generate.
Note that this is slightly different from #Severin's answer--he's mentioning std::transform instead of std::generate. The basic difference between the two is that transform takes a set of inputs, transforms them, and produces a set of those outputs. Your AnInstance::run just produces outputs (without taking any inputs) so at least to me it seems like std::generate is a better fit.
std::transform would be more useful if you had something like this:
std::vector<int> inputs { 1, 2, 3, 4, 5};
std::vector<int> results;
std::transform(inputs.begin(), inputs.end(), [](int in) { return in * 2; });
This should produce results of 2, 4, 6, 8, 10.
The only conceptual error you have is trying to get polymorphic behaviour when invoking virtual functions through objects, as opposed to pointers or references to said objects. In C++, to get run-time polymorphism, you need to work with pointers or references. Thus, Processor should work with a std::vector<AnInterface<R>*> like this:
template<typename R>
class Processor
{
public:
Processor(std::vector<AnInterface<R>*> toRun) : toRun(toRun) {}
std::vector<R> process() {
std::vector<R> res;
for(int i = 0; i < this->toRun.size(); ++i)
res.push_back(toRun[i]->run());
return res;
}
private:
std::vector<AnInterface<R>*> toRun;
};
Here's a fixed version of your code.
Another thing to note : when using overriding a virtual function in a derived class, mark the override with the eponymous keyword. This helps the compiler help you.
Do you really need Processor class? What I would propose to use std::transform
std::transform applies the given function to a range and stores the result in another range
vector<AnInterface<R>> does not work because it causes slicing. This is also the cause of your error messages, because some vector operations require to default-construct or copy-construct objects and that is not possible with an abstract class.
Probably vector<shared_ptr<AnInterface<R>>> best matches your intent. shared_ptr is the closest thing C++ has to a Java object reference.
Here is working code in C++11 based on your sample code. One point I would have is that Processor currently takes its vector by value. It could take this by reference, or even by moving, if that better matched your design.
#include <iostream>
#include <memory>
#include <vector>
template<typename R>
struct AnInterface
{
virtual R run() = 0;
virtual ~AnInterface() {}
};
template<typename R>
using AnInterfaceVector = std::vector< std::shared_ptr<AnInterface<R>> >;
template<typename R>
class Processor
{
public:
Processor(AnInterfaceVector<R> toRun) : toRun(toRun) {}
std::vector<R> process()
{
std::vector<R> res;
for (auto && r : toRun)
res.push_back( r->run() );
return res;
}
private:
AnInterfaceVector<R> toRun;
};
struct AnInstanceClass : AnInterface<int>
{
int run() override { return temp; }
AnInstanceClass(int n): temp(n) {}
int temp;
};
int main()
{
AnInterfaceVector<int> toRun;
toRun.emplace_back( std::make_shared<AnInstanceClass>(4) );
toRun.emplace_back( std::make_shared<AnInstanceClass>(7) );
Processor<int> p{toRun};
auto results = p.process();
for (auto && i : results)
std::cout << i << " ";
std::cout << std::endl;
}
NB. I don't offer any claim whether this is better or worse than using a different pattern as other answers have suggested; this is just a working version of the code you were trying to write.
As was already mentioned in the other answers, your error was trying to use a vector of interfaces (std::vector<AnInterface<int>>) instead of a vector of pointers to interfaces like std::vector<AnInterface<int>*> - with only the latter allowing polymorphism, whereas your version would try to store actual Interface objects (which is of course not posssible as they are abstract classes).
I wanted to mention in addition, that there is a nice pattern by Sean Parent that makes it unnecessary for your AnInstanceClass to inhereit from anything, as long as it implements a member function with the correct name and signature. This is quite handy, because you can e.g. even use lambdas or plain functions (after wrapping them in a std::function) which cannot inherit from anything:
#include <vector>
#include <memory>
#include <iostream>
#include <algorithm>
#include <functional>
//R is the return type
template<class R>
class Processor {
public:
//T can be anything, that has an ()-operator
template<class T>
void push_back(const T& arg) {
todo.emplace_back(std::make_unique<runnable_imp<T>>(arg));
}
std::vector<R> process() {
std::vector<R> ret;
for (auto& e : todo) {
ret.push_back(e->run());
}
return ret;
}
private:
struct runnable_concept {
virtual R run()=0;
virtual ~runnable_concept(){};
};
template<class T>
struct runnable_imp :public runnable_concept {
runnable_imp(T data) :data(data){};
virtual R run() override { return data(); }
T data;
};
std::vector<std::unique_ptr<runnable_concept>> todo;
};
struct SomeClass {
SomeClass(int arg) :arg(arg){};
int operator()(){ return arg; }
int arg;
};
int SomeFunction(){ return 30; }
int main()
{
Processor<int> pr;
pr.push_back([]{return 10; });
pr.push_back(SomeClass(20));
pr.push_back(std::function<int()>(SomeFunction));
std::vector<int> res= pr.process();
for (auto e : res) {
std::cout << e << std::endl;
}
}