C++ smart pointer const correctness - c++

I have a few containers in a class, for example, vector or map which contain
shared_ptr's to objects living on the heap.
For example
template <typename T>
class MyExample
{
public:
private:
vector<shared_ptr<T> > vec_;
map<shared_ptr<T>, int> map_;
};
I want to have a public interface of this class that sometimes returns shared_ptrs to const objects (via shared_ptr<const T>) and sometimes shared_ptr<T> where I allow the caller to mutate the objects.
I want logical const correctness, so if I mark a method as const, it cannot change the objects on the heap.
Questions:
1) I am confused by the interchangeability of shared_ptr<const T> and shared_ptr<T>. When someone passes a shared_ptr<const T> into the class, do I:
Store it as a shared_ptr<T> or shared_ptr<const T> inside the container?
OR
Do I change the map, vector types (e.g. insert_element(shared_ptr<const T> obj)?
2) Is it better to instantiate classes as follows: MyExample<const int>? That seems unduly restrictive, because I can never return a shared_ptr<int>?

shared_ptr<T> and shared_ptr<const T> are not interchangable. It goes one way - shared_ptr<T> is convertable to shared_ptr<const T> but not the reverse.
Observe:
// f.cpp
#include <memory>
int main()
{
using namespace std;
shared_ptr<int> pint(new int(4)); // normal shared_ptr
shared_ptr<const int> pcint = pint; // shared_ptr<const T> from shared_ptr<T>
shared_ptr<int> pint2 = pcint; // error! comment out to compile
}
compile via
cl /EHsc f.cpp
You can also overload a function based on a constness. You can combine to do these two facts to do what you want.
As for your second question, MyExample<int> probably makes more sense than MyExample<const int>.

I would suggest the following methotology:
template <typename T>
class MyExample
{
private:
vector<shared_ptr<T> > data;
public:
shared_ptr<const T> get(int idx) const
{
return data[idx];
}
shared_ptr<T> get(int idx)
{
return data[idx];
}
void add(shared_ptr<T> value)
{
data.push_back(value);
}
};
This ensures const-correctness. Like you see the add() method does not use <const T> but <T> because you intend the class to store Ts not const Ts. But when accessing it const, you return <const T> which is no problem since shared_ptr<T> can easily be converted to shared_ptr<const T>. And sice both get() methods return copies of the shared_ptr's in your internal storage the caller can not accidentally change the object your internal pointers point to. This is all comparable to the non-smart pointer variant:
template <typename T>
class MyExamplePtr
{
private:
vector<T *> data;
public:
const T *get(int idx) const
{
return data[idx];
}
T *get(int idx)
{
return data[idx];
}
void add(T *value)
{
data.push_back(value);
}
};

If someone passes you a shared_ptr<const T> you should never be able to modify T. It is, of course, technically possible to cast the const T to just a T, but that breaks the intent of making the T const. So if you want people to be able to add objects to your class, they should be giving you shared_ptr<T> and no shared_ptr<const T>. When you return things from your class you do not want modified, that is when you use shared_ptr<const T>.
shared_ptr<T> can be automatically converted (without an explicit cast) to a shared_ptr<const T> but not the other way around. It may help you (and you should do it anyway) to make liberal use of const methods. When you define a class method const, the compiler will not let you modify any of your data members or return anything except a const T. So using these methods will help you make sure you didn't forget something, and will help users of your class understand what the intent of the method is. (Example: virtual shared_ptr<const T> myGetSharedPtr(int index) const;)
You are correct on your second statement, you probably do not want to instantiate your class as <const T>, since you will never be able to modify any of your Ts.

one thing to realize is that:
tr1::shared_ptr<const T> is mimicking the functionality of T const * namely what it points to is const, but the pointer itself isn't.
So you can assign a new value to your shared pointer, but I would expect that you wouldn't be able to use the dereferenced shared_ptr as an l-value.

Prologue
The const qualifier changes the behaviour of std::shared_ptr, just like it affects the legacy C pointers.
Smart pointers should be managed and stored using the right qualifiers at all times to prevent, enforce and help programmers to treat them rightfully.
Answers
When someone passes a shared_ptr<const T> into the class, do I store it as a shared_ptr<T> or shared_ptr<const T> inside the vector and map or do I change the map, vector types?
If your API accepts a shared_ptr<const T>, the unspoken contract between the caller and yourself is that you are NOT allowed to change the T object pointed by the pointer, thus, you have to keep it as such in your internal containers, e.g. std::vector<std::shared_ptr<const T>>.
Moreover, your module should NEVER be able/allowed to return std::shared_ptr<T>, even though one can programatically achieve this (See my answer to the second question to see how).
Is it better to instantiate classes as follows: MyExample<const int>? That seems unduly restrictive, because I can never return a shared_ptr<int>?
It depends:
If you designed your module so that objects passed to it should not change again in the future, use const T as the underlying type.
If you your module should be able to return non-const T pointers, you should use T as your underlying type and probably have two different getters, one that returns mutable objects (std::shared_ptr<T>) and another that returns non-mutable objects (std::shared_ptr<const T>).
And, even though I hope we just agreed you should not return std::shared_ptr<T> if you have a const T or std::shared_ptr<const T>, you can:
const T a = 10;
auto a_ptr = std::make_shared<T>(const_cast<T>(a));
auto b_const_ptr = std::make_shared<const T>();
auto b_ptr = std::const_pointer_cast<T>(b_const_ptr);
Full blown example
Consider the following example that covers all the possible permutations of const with std::shared_ptr:
struct Obj
{
int val = 0;
};
int main()
{
// Type #1:
// ------------
// Create non-const pointer to non-const object
std::shared_ptr<Obj> ptr1 = std::make_shared<Obj>();
// We can change the underlying object inside the pointer
ptr1->val = 1;
// We can change the pointer object
ptr1 = nullptr;
// Type #2:
// ------------
// Create non-const pointer to const object
std::shared_ptr<const Obj> ptr2 = std::make_shared<const Obj>();
// We cannot change the underlying object inside the pointer
ptr2->val = 3; // <-- ERROR
// We can change the pointer object
ptr2 = nullptr;
// Type #3:
// ------------
// Create const pointer to non-const object
const std::shared_ptr<Obj> ptr3 = std::make_shared<Obj>();
// We can change the underlying object inside the pointer
ptr3->val = 3;
// We can change the pointer object
ptr3 = nullptr; // <-- ERROR
// Type #4:
// ------------
// Create const pointer to non-const object
const std::shared_ptr<const Obj> ptr4 = std::make_shared<const Obj>();
// We can change the underlying object inside the pointer
ptr4->val = 4; // <-- ERROR
// We can change the pointer object
ptr4 = nullptr; // <-- ERROR
// Assignments:
// ------------
// Conversions between objects
// We cannot assign to ptr3 and ptr4, because they are const
ptr1 = ptr4 // <-- ERROR, cannot convert 'const Obj' to 'Obj'
ptr1 = ptr3;
ptr1 = ptr2 // <-- ERROR, cannot convert 'const Obj' to 'Obj'
ptr2 = ptr4;
ptr2 = ptr3;
ptr2 = ptr1;
}
Note: The following is true when managing all types of smart pointers. The assignment of pointers might differ (e.g. when handling unique_ptr), but the concept it the same.

In the meantime, there exists std::experimental::propagate_const that addresses exactly this issue.
#include <vector>
#include <map>
#include <memory>
#include <experimental/propagate_const>
#include <type_traits>
template <typename T>
class MyExample
{
public:
template <typename U>
using pointer_t = std::experimental::propagate_const<std::shared_ptr<U>>;
std::vector<pointer_t<T> > vec_;
std::map<pointer_t<T>, int> map_;
};
int main() {
auto x = std::make_shared<int>(42);
MyExample<int> e;
e.vec_.push_back(x);
// non-const getter will propagate mutability through the pointer
{
auto& test = e.vec_[0];
static_assert(std::is_same<int&, decltype(*test)>::value);
}
// const-getter will propagate const through the pointer
{
MyExample<int> const& ec = e;
auto& test = ec.vec_[0];
static_assert(std::is_same<int const&, decltype(*test)>::value);
}
return 0;
}
https://godbolt.org/z/ej3hPsqMo
If you are uncomfortable using the experimental namespace, or if you are using MSVC (as far as i know MSVC hasn't implemented this feature yet), you can implement your own version of propagate_const. A protoype could look like this:
template <typename Ptr>
class propagate_const
{
public:
using value_type = typename std::remove_reference<decltype(*Ptr{})>::type;
template <
typename T,
typename = std::enable_if_t<std::is_convertible<T, Ptr>::value>
>
constexpr propagate_const(T&& p) : ptr{std::forward<T>(p)} {}
constexpr value_type& operator*() { return *ptr; }
constexpr value_type const& operator*() const { return *ptr; }
constexpr value_type& operator->() { return *ptr; }
constexpr value_type const& operator->() const { return *ptr; }
private:
Ptr ptr;
};
https://godbolt.org/z/rbKcr3M66

Related

Efficiently erase a unique_ptr from an unordered_set

I am storing the ownership of some objects inside an unordered_set, using unique_ptrs.
But I don't know a good way to erase one of them from the set, when the time comes.
Code looks something like this:
typedef unique_ptr<MyType> MyPtr;
unordered_set<MyPtr> owner;
MyPtr p = make_unique<MyType>("foo")
MyType *pRaw = p.get();
owner.insert(std::move(p));
// Later ...
// I want to do something like this (cannot be written as-is, of course):
// owner.erase(pRaw);
Is there a way to do this?
I can, of course, iterate the entire set with begin() and end(), but the whole point of putting them in the set is to make these lookups efficient.
Some things I have thought of already:
Use shared_ptr. This is the wrong abstraction for my case. Ownership is unique.
Use raw pointers, and forget about unique_ptr. This abandons all the advantages that unique_ptr provides.
Find the bucket with unordered_set::begin(key). As far as I know, there is no way for me to create a key that will match the unique_ptr I want to delete. But I'm happy to be proven wrong (:
(In truth, I solved this using eastl::unordered_set, with its find_as function for custom keys)
This is a tough case. erase has an overload that takes a const key_type& parameter, so we can try to create a "stale" unique_ptr to get the hash value of the element to be erased:
template <typename T>
auto erase(std::unordered_set<std::unique_ptr<T>>& set, T* ptr)
{
std::unique_ptr<T> stale_ptr{ptr};
auto ret = set.erase(stale_ptr);
stale_ptr.release();
return ret;
}
(live demo)
This version, however, is not exception safe in general, because release will not be called if set.erase throws an exception. This is not a problem in this case, since std::equal_to<std::unique_ptr<T>>::operator() never throws exception. In the general case, we can abuse unique_ptr (!) to enforce exception safety by ensuring that release is called regardless of whether the function is exited normally or exceptionally:
template <typename T>
auto erase(std::unordered_set<std::unique_ptr<T>>& set, T* ptr)
{
std::unique_ptr<T> stale_ptr{ptr};
auto release = [](std::unique_ptr<T>* p) { p->release(); };
std::unique_ptr<std::unique_ptr<T>, decltype(release)> release_helper{&stale_ptr, release};
return set.erase(stale_ptr);
}
(live demo)
In C++20, std::unordered_set::find can use equivalent key with transparent hash and KeyEqual, then you might do something similar to:
struct MyHash
{
using is_transparent = void;
auto operator()(MyType* p) const { return std::hash<MyType*>{}(p); }
auto operator()(const MyPtr& p) const { return std::hash<MyType*>{}(p.get()); }
};
struct MyEqual
{
using is_transparent = void;
template <typename LHS, typename RHS>
auto operator()(const LHS& lhs, const RHS& rhs) const
{
return AsPtr(lhs) == AsPtr(rhs);
}
private:
static const MyType* AsPtr(const MyType* p) { return p; }
static const MyType* AsPtr(const MyPtr& p) { return p.get(); }
};
int main()
{
std::unordered_set<MyPtr, MyHash, MyEqual> owner;
MyPtr p = std::make_unique<MyType>();
MyType *pRaw = p.get();
owner.insert(std::move(p));
auto it = owner.find(pRaw);
if (it != owner.end()) {
owner.erase(it);
}
}

Do C++17 and newer std::allocators work for a dynamic number of custom heaps?

Here is my scenario:
There is a set of Instance objects, the number of the objects being determined at run time.
For each of those instances, a custom heap is created. (Yes, I refer to ::HeapCreate() et. all win32 functions).
struct Instance
{
HANDLE heap;
};
Now, there is some Instance specific State object which has access (a pointer) to the Instance. Its a 1:1 relationship. 1 State per Instance.
struct State
{
Instance * inst;
};
In that State, I want to use STL containers like std::vector and alike, but I want those to use an allocator, which uses the heap created by the instance.
Reading up on the history of std::allocator from the times of C++98 up to C++17 and newer, some old requirements have been dropped, but my take away is, that it is not possible to write such an allocator for my above shown use case. Given that the Allocator would be of the same type each time, but stateful (as in instances using different heap HANDLEs).
So here my question(s):
Can I/ should I even try to use the STL allocator stuff for this use case or better just roll my select few own container classes instead?
I stumbled across that std::pmr C++17 namespace and wonder if there is stuff in there I am not yet acquainted with, which would help me with my use case.
Your answers will be very valuable if you show the "idiomatic" way how to solve this problem in modern (c++17 and newer) C++.
Here, my incomplete and current "work in progress". I will fill in more over time, based on answers and comments and my own progress.
#include <Windows.h>
struct custom_heap_allocator
{
HANDLE heap;
// No default constructor
custom_heap_allocator() = delete;
// an instance is tied to a heap handle.
explicit custom_heap_allocator(HANDLE h)
: heap{ h }
{
}
// can I get away with this....
// copy constructor for same type.
custom_heap_allocator(const custom_heap_allocator& other)
: heap{ other.heap }
{
}
//... or do I need something like this? Or is it somehow covered by rebind?
//template<class U>
//custom_heap_allocator(const custom_heap_allocator<U>& other)
// : heap{ other.heap }
//{
//}
template<class U>
struct rebind {
typedef custom_heap_allocator other;
};
};
template <class T, class U>
constexpr bool operator== (const custom_heap_allocator& a1, const custom_heap_allocator& a2) noexcept
{
return a1.heap == a2.heap;
}
template <class T, class U>
constexpr bool operator!= (const custom_heap_allocator& a1, const custom_heap_allocator& a2) noexcept
{
return a1.heap != a2.heap;
}
Note: shutting down an Instance will simply destroy the heap (no need to call destructors for stuff still on the heap, as those are just simple data and not system resources etc. And all dynamic stuff they hold are also from the same heap instance).
Here is a more complete version of the allocator from your (edited) question.
Some basic unit tests using std::vector and std::list at cpp.sh/4viaj
template<typename T>
struct custom_heap_allocator
{
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
// No default constructor
custom_heap_allocator() = delete;
// An instance is tied to a heap handle.
explicit custom_heap_allocator(HANDLE h)
: heap{ h }
{
}
// Instances are copy-constructable and copy-assignable.
custom_heap_allocator(const custom_heap_allocator&) = default;
custom_heap_allocator& operator=(const custom_heap_allocator&) = default;
// All related allocators share the same heap, regardless of type.
template<class U>
custom_heap_allocator(const custom_heap_allocator<U>& other)
: heap{ other.heap }
{
}
// Allocate and deallocate space for objects using the heap.
T* allocate(size_t n) {
return static_cast<T*>(HeapAlloc(heap, 0, sizeof(T) * n));
}
void deallocate(T* ptr, size_t n) {
HeapFree(heap, 0, ptr);
}
// Construct and destroy objects in previously allocated space.
// This *should* be optional and provided by std::allocator_traits,
// but it looks like some std containers don't use the traits.
template< class U, class... Args >
void construct( U* p, Args&&... args ) {
::new((void *)p) U(std::forward<Args>(args)...);
}
template< class U >
void destroy( U* p ) {
p->~U();
}
// Template for related allocators of different types.
template<class U>
struct rebind {
typedef custom_heap_allocator<U> other;
};
private:
// Heap used for all allocations/deallocations.
HANDLE heap;
// Allow all related types to access our private heap.
template<typename> friend struct custom_heap_allocator;
};

Custom hash that works with pointer and reference

I want to create a reusable IdHash and IdEqualTo class that takes const instances (reference, raw pointer, or smart pointer), and returns the hash value or the compare result.
template<class Entity, class Id>
struct IdFunc {
typedef typename std::function<const Id& (const Entity&)> type;
};
template<class Entity, class Id>
struct IdHash {
public:
explicit IdHash(const typename IdFunc<Entity, Id>::type& idFunc) : idFunc_(idFunc) {}
std::size_t operator()(const Entity& o) const {
return std::hash<Id>()(idFunc_(o));
}
private:
typename IdFunc<Entity, Id>::type idFunc_;
};
// IdEqualTo follows the same pattern
My first attempt works with unordered_set<MyClass, IdHash<string, MyClass>, IdEqualTo<...>>. Since MyClass will be a inheritance hierarchy instead of a single type, I need to switch to pointers: unordered_set<unique_ptr<MyClass>, IdHash<string, MyClass>, ...>. Now I need a version of the operator that takes unique_ptr&. I provided the following
std::size_t operator()(const Entity* o) const {
return std::hash<Id>()(idFunc_(*o));
}
hoping unique_ptr<MyClass>& can somehow be converted to MyClass*. It didn't work. Since this utility is supposed to transcend storage type, how can I make it work with reference, raw pointer, or smart pointer?
See code sample.
Thanks.
There is no automatic conversion from smart pointers to raw pointers (though you can use get()).
Specialize your templates for smart pointers, this is the way it is done in standard library and in boost.
template <class Inner, class Id>
struct IdHash<std::unique_ptr<Inner>, Id> {
typedef std::unique_ptr<Inner> PtrType;
std::size_t operator() (const PtrType &pointer) const {
return std::hash<Id>()(idFunc_(pointer.get());
}
};
You may also want to create std::hash<Id> instance only once instead of every time you call operator().

C++ returning non const reference out of const object

I have structure that contains reference in it
template <class T>
struct RefContainer {
RefContainer(T& t) : _r(t) {}
T& getRef() {
return _r;
}
private:
T& _r;
};
Now, another object, which is immutable uses this structure inside itself and has this object in it like so:
RefContainer<char> _c;
When I use that immutable object to transform itself with a dot I get a const reference. Since I call getRef of RefContainer object inside the immutable objects compiler says I violate const correctness.
The RefContainer itself has to hold non-const lvalue reference but I'd love to chain calls on immutable object to create new ones like so:
ImmubableObject obj;
auto newObj = obj.copyWithSomeAttributes().modifyWithThisString("str");
// I'm on C++11 btw, so I can use everything C++11 has to offer
How do I work this out the "right" way (possibly avoiding ugly const casts) ?
You should try something like this:
template <class T>
struct RefContainer {
RefContainer(T& t) : _r(t) {}
T& getRef() const {
// ^^^^^
return _r;
}
private:
T& _r;
};
This way, the T& reference can be used as non const, regardless the RefContainer instance is a const object or not.

Using a std::unordered_set of std::unique_ptr

Assume I have a set of unique_ptr:
std::unordered_set <std::unique_ptr <MyClass>> my_set;
I'm not sure what's the safe way to check if a given pointer exists in the set. The normal way to do it may be to call my_set.find (), but what do I pass as a parameter?
All I have from the outside is a raw pointer. So I have to create another unique_ptr from the pointer, pass it to find() and then release() that pointer, otherwise the object would get destructed (twice). Of course, this process can be done in a function, so the caller can pass the raw pointer and I do the conversions.
Is this method safe? Is there a better way to work with a set of unique_ptr?
You can also use a deleter that optionally doesn't do anything.
template<class T>
struct maybe_deleter{
bool _delete;
explicit maybe_deleter(bool doit = true) : _delete(doit){}
void operator()(T* p) const{
if(_delete) delete p;
}
};
template<class T>
using set_unique_ptr = std::unique_ptr<T, maybe_deleter<T>>;
template<class T>
set_unique_ptr<T> make_find_ptr(T* raw){
return set_unique_ptr<T>(raw, maybe_deleter<T>(false));
}
// ...
int* raw = new int(42);
std::unordered_set<set_unique_ptr<int>> myset;
myset.insert(set_unique_ptr<int>(raw));
auto it = myset.find(make_find_ptr(raw));
Live example.
Note that the ability to do heterogenous lookups on standard containers is subject of some proposals.
http://cplusplus.github.io/LWG/lwg-proposal-status.html lists
N3465 Adding heterogeneous comparison lookup to associative containers for TR2 (Rev 2) [Handle with N3573]
N2882 id.
N3573 Heterogenous extensions to unordered containers [Handle with N3465]
Especially the latter looks like it would cover your use case.
For now, here is an IMO not very pretty but working alternative workaround (O(n)):
#include <iterator>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <memory>
#include <cassert>
struct MyClass {};
template <typename T>
struct RawEqualTo
{
RawEqualTo(T const* raw) : raw(raw) {}
bool operator()(T const* p) const
{ return raw == p; }
bool operator()(std::unique_ptr<T> const& up) const
{ return raw == up.get(); }
private:
T const* raw;
};
using namespace std;
int main()
{
std::unordered_set <std::unique_ptr <MyClass>> my_set;
my_set.insert(std::unique_ptr<MyClass>(new MyClass));
my_set.insert(std::unique_ptr<MyClass>(new MyClass));
auto raw = my_set.begin()->get();
bool found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw));
assert(found);
raw = new MyClass;
found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw));
assert(!found);
delete raw;
}
Warning It's also very inefficient, of course.
You can use a std::map<MyClass*, std::unique_ptr<MyClass>> instead of a set. Then you can add elements like this:
std::unique_ptr<MyClass> instance(new MyClass);
map.emplace(instance.get(), std::move(instance));
If the goal is constant time for the look up, I don't think that
there is a solution.
std::unordered_set<std::unique_ptr<MyClass>>::find requires an
std::unique_ptr<MyClass> as argument. You will have to either change
the container, or change the contained type.
One possibility might be to replace std::unique_ptr with
std::shared_ptr, and change the rest of the code so that all
MyClass are put into a shared_ptr as soon as they are created,
and are only manipulated through shared pointers. Logically,
this is probably more coherent anyway: unique_ptr pretty much
implies (by its name, as well as its semantics) that there
aren't other pointers to the object. On the other hand, you may
not be able to use shared_ptr, if e.g. MyClass has pointers to
other MyClass, which may build a cycle.
Otherwise, if you can accept O(lg n) access, rather than
constant access (the difference generally doesn't become
noticeable until the tables are fairly large), you can use an
std::vector<MyClass>, using std::lower_bound to keep it
sorted. Unlike std::unordered_set<>::find, std::lower_bound
does not require the target value to have the same type as the
value_type of the sequence; all you have to do is to ensure
that they are comparable, say by providing a Compare object
along the lines of:
class MyClassPtrCompare
{
std::less<MyClass const*> cmp;
public:
bool operator()( std::unique_ptr<MyClass> const& lhs,
std::unique_ptr<MyClass> const& rhs ) const
{
return cmp( lhs.get(), rhs.get() );
}
bool operator()( MyClass const* lhs,
std::unique_ptr<MyClass> const& rhs ) const
{
return cmp( lhs, rhs.get() );
}
bool operator()( std::unique_ptr<MyClass> const& lhs,
MyClass const* rhs ) const
{
return cmp( lhs.get(), rhs );
}
bool operator()( MyClass const* lhs,
MyClass const* rhs ) const
{
return cmp( lhs, rhs );
}
};
Insertion may involve a number of moves, but moving
a std::unique_ptr should be fairly cheap, and the improved
locality of this solution might offset the additional runtime
costs it otherwise imposes.
If you can use Abseil, do it:
absl::flat_hash_set<std::unique_ptr<MyClass>> my_set;
just works :)
Here is the proper way to do it in C++20 with "Heterogeneous lookup for unordered containers" available:
struct Hash {
using is_transparent = void;
template <class P>
size_t operator()(const P& p) const {
return std::hash<P>{}(p);
}
};
struct KeyEqual {
using is_transparent = void;
template <class P, class Q>
bool operator()(const P& lhs, const Q& rhs) const {
return std::to_address(lhs) == std::to_address(rhs);
}
};
std::unordered_set<std::unique_ptr<MyClass>, Hash, KeyEqual> my_set;
More on the topic (in Russian): https://www.coursera.org/learn/c-plus-plus-brown/supplement/TtrLN/unordered-set-unique-ptr