Unique_Ptr Boost Equivalent

unique_ptr boost equivalent?

It's not possible to create something like unique_ptr without C++0x (where it's part of the standard library, and so Boost doesn't need to provide it).

Specifically without rvalue references, which are a feature in C++0x, a robust implementation of unique_ptr is impossible, with or without Boost.

In C++03, there are a few possible alternatives, although each have their flaws.

  • boost::shared_ptr is probably the simplest replacement in terms of capabilites. You can safely use it anywhere you'd otherwise use a unique_ptr and it'd work. It just wouldn't be as efficient, because of the added reference counting. But if you're looking for a simple drop-in replacement that's able to handle everything unique_ptr can do, this is probably your best bet. (Of course, a shared_ptr can do a lot more as well, but it can also simply be used as a drop-in replacement for unique_ptr.)
  • boost::scoped_ptr is similar to unique_ptr but does not allow transfer of ownership. It works great as long as the smart pointer is meant to retain exclusive ownership throughout its lifetime.
  • std::auto_ptr works very similar to unique_ptr, but has a few limitations, mainly that it can not be stored in standard library containers. If you're simply looking for a pointer that allows transfer of ownership, but which is not meant to be stored in containers or copied around, this is probably a good bet.

Using a unique_ptr without C++11

Well, boost::movelib::unique_ptr is part of the Boost.Move library which offers "Portable move semantics for C++03 and C++11 compilers". Since unique_ptr clearly needs move semantic, this looks like your best choice.

Boost graph bundled properties with std::unique_ptr

Why is the copy constructor is necessary for list initialization in the first place? Is there something I do not understand about it, or is this a shortcoming of boost-graph?

That's not a thing and also not what's happening.

Aside: in copy-initialization the assignment can be elided by the compiler, but operator= still needs to be accessible for it to be valid code.

But in this case it's just about the library code not being move-aware. You have to realize that "bundled" properties are separately stored. The bundle (as any property) is required to be default constructible anyways (so add_vertex(g) works) so the implementation is simplified by always assigning to the default-constructed property.

Since it's not move-aware, assignment will not forward the rvalue and things don't compile.

OPTIONS


  1. The linked answer already showd:

    if(g[v].node_logic) {
    g[v].node.reset(new custom_node(g[v].vertex_name, 0, standby, normal));
    }
  2. More options:

    VertexProperties props {0,
    std::make_unique<custom_node>("inner", 2)};
    auto vd = boost::add_vertex(g);
    g[vd] = std::move(props);
  3. Wrap yo' shit! You can create any interface you prefer:

    auto add_vertex = [&g](VertexProperties&& props) {
    auto vd = boost::add_vertex(g);
    g[vd] = std::move(props);
    return vd;
    };

    add_vertex({0, std::make_unique<custom_node>("inner", 2)});
  4. You can even elaborate on that:

    auto add_vertex = [&g](int id, std::string name, int capacity = -1) {
    auto vd = boost::add_vertex(g);
    g[vd] = { id, std::make_unique<custom_node>(name, capacity) };
    return vd;
    };

    add_vertex(0, "inner", 2);
    add_vertex(1, "outer", 3);
    add_vertex(2, "other");

All the above options Live On Coliru

The latter interface is far superior anyways, if you ask me.

If you want you can use ADL to make it available to others:

Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <memory>

namespace MyLib { // for ADL demo
struct custom_node {
custom_node(std::string name, int capacity)
: name(std::move(name)), capacity(capacity) {}

std::string name = "uninitialized";
int capacity = -1;

friend std::ostream& operator<<(std::ostream& os, custom_node const& cn) {
return os << "{" << std::quoted(cn.name) << ", " << cn.capacity << "}";
}
};

struct VertexProperties {
int id{};
std::unique_ptr<custom_node> node;

friend std::ostream& operator<<(std::ostream& os, VertexProperties const& vp) {
os << vp.id;
if (vp.node)
os << ", " << *vp.node;
return os;
}
};

template <typename G>
auto add_vertex(G& g, int id, const std::string& name, int capacity = -1) {
auto vd = boost::add_vertex(g);
g[vd] = { id, std::make_unique<custom_node>(name, capacity) };
return vd;
}
}

using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, MyLib::VertexProperties>;
using custom_vertex = Graph::vertex_descriptor;
using custom_edge = Graph::edge_descriptor;

int main() {
Graph g;

add_vertex(g, 10, "inner", 2);
add_vertex(g, 11, "outer", 3);
add_vertex(g, 12, "other");

for (auto vd : boost::make_iterator_range(vertices(g))) {
std::cout << g[vd] << "\n";
}
}

Prints

10, {"inner", 2}
11, {"outer", 3}
12, {"other", -1}

SOMEONE MENTIONED DESIGN?

If all you want is unique ownership with optional/lazy construction, why not:

struct VertexProperties {
int id{};
std::optional<custom_node> node;
};

Or even just

struct VertexProperties {
int id{};
custom_node node;
};

The ownership semantics will be the same, without the costs:

Graph g;

add_vertex({10, custom_node{"inner", 2}}, g);
add_vertex({11, custom_node{"outer", 3}}, g);
add_vertex({12, custom_node{"other"}}, g);

That's just using the standard boost::add_vertex overloads from BGL. Without optional<> it can become even simpler:

add_vertex({10, {"inner", 2}}, g);
add_vertex({11, {"outer", 3}}, g);
add_vertex({12, {"other"}}, g);

Also Live On Coliru (without std::optional: Live)

#include <boost/graph/adjacency_list.hpp>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <memory>
#include <optional>

struct custom_node {
custom_node(std::string name, int capacity = -1)
: name(std::move(name)), capacity(capacity) {}

std::string name = "uninitialized";
int capacity = -1;

friend std::ostream& operator<<(std::ostream& os, custom_node const& cn) {
return os << "{" << std::quoted(cn.name) << ", " << cn.capacity << "}";
}
};

struct VertexProperties {
int id{};
std::optional<custom_node> node;

friend std::ostream& operator<<(std::ostream& os, VertexProperties const& vp) {
os << vp.id;
if (vp.node) os << ", " << *vp.node;
return os;
}
};

using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexProperties>;
using custom_vertex = Graph::vertex_descriptor;
using custom_edge = Graph::edge_descriptor;

int main() {
Graph g;

add_vertex({10, custom_node{"inner", 2}}, g);
add_vertex({11, custom_node{"outer", 3}}, g);
add_vertex({12, custom_node{"other"}}, g);

for (auto vd : boost::make_iterator_range(vertices(g))) {
std::cout << g[vd] << "\n";
}
}

Prints

10, {"inner", 2}
11, {"outer", 3}
12, {"other", -1}

stl container with std::unique_ptr's vs boost::ptr_container

They really solve two similar but different problems.

A pointer container is a way to store objects in a container that just so happen to be pointers to allocated memory rather than values. They do everything in their power to hide the fact that they are a container of pointers. This means:

  • Entries in the container cannot be NULL.
  • Values you get from iterators and functions are references to the type, not pointers to the type.
  • Working with many standard algorithms can be... tricky. And by "tricky", I mean broken. Pointer containers have their own built-in algorithms.

However, the fact that pointer containers know that they're containers of pointers, they can offer some new functionality:

  • A clone member function that performs a deep copy, via the use of a certain "Cloneable" concept on the type of the object.
  • The ability of a container to release ownership of its objects (after a shallow copy, for example).
  • Built-in functions to transfer ownership to other containers.

They really are quite different concepts. There is a lot of stuff you would have to do manually that pointer containers can do automatically with specialized functions.

If you really need a container of pointers, then you can use containers of unique_ptr. But if you need to store a bunch of objects that you happen to heap allocate, and you want to play special games with them involving ownership and such, then the pointer containers are not a bad idea.

Boost.Python: How to expose std::unique_ptr

In short, Boost.Python does not support move-semantics, and therefore does not support std::unique_ptr. Boost.Python's news/change log has no indication that it has been updated for C++11 move-semantics. Additionally, this feature request for unique_ptr support has not been touched for over a year.

Nevertheless, Boost.Python supports transferring exclusive ownership of an object to and from Python via std::auto_ptr. As unique_ptr is essentially a safer version of auto_ptr, it should be fairly straight forward to adapt an API using unique_ptr to an API that uses auto_ptr:

  • When C++ transfers ownership to Python, the C++ function must:
    • be exposed with CallPolicy of boost::python::return_value_policy with a boost::python::manage_new_object result converter.
    • have unique_ptr release control via release() and return a raw pointer
  • When Python transfers ownership to C++, the C++ function must:
    • accept the instance via auto_ptr. The FAQ mentions that pointers returned from C++ with a manage_new_object policy will be managed via std::auto_ptr.
    • have auto_ptr release control to a unique_ptr via release()

Given an API/library that cannot be changed:

/// @brief Mockup Spam class.
struct Spam;

/// @brief Mockup factory for Spam.
struct SpamFactory
{
/// @brief Create Spam instances.
std::unique_ptr<Spam> make(const std::string&);

/// @brief Delete Spam instances.
void consume(std::unique_ptr<Spam>);
};

The SpamFactory::make() and SpamFactory::consume() need to be wrapped via auxiliary functions.

Functions transferring ownership from C++ to Python can be generically wrapped by a function that will create Python function objects:

/// @brief Adapter a member function that returns a unique_ptr to
/// a python function object that returns a raw pointer but
/// explicitly passes ownership to Python.
template <typename T,
typename C,
typename ...Args>
boost::python::object adapt_unique(std::unique_ptr<T> (C::*fn)(Args...))
{
return boost::python::make_function(
[fn](C& self, Args... args) { return (self.*fn)(args...).release(); },
boost::python::return_value_policy<boost::python::manage_new_object>(),
boost::mpl::vector<T*, C&, Args...>()
);
}

The lambda delegates to the original function, and releases() ownership of the instance to Python, and the call policy indicates that Python will take ownership of the value returned from the lambda. The mpl::vector describes the call signature to Boost.Python, allowing it to properly manage function dispatching between the languages.

The result of adapt_unique is exposed as SpamFactory.make():

boost::python::class_<SpamFactory>(...)
.def("make", adapt_unique(&SpamFactory::make))
// ...
;

Generically adapting SpamFactory::consume() is a more difficult, but it is easy enough to write a simple auxiliary function:

/// @brief Wrapper function for SpamFactory::consume_spam().  This
/// is required because Boost.Python will pass a handle to the
/// Spam instance as an auto_ptr that needs to be converted to
/// convert to a unique_ptr.
void SpamFactory_consume(
SpamFactory& self,
std::auto_ptr<Spam> ptr) // Note auto_ptr provided by Boost.Python.
{
return self.consume(std::unique_ptr<Spam>{ptr.release()});
}

The auxiliary function delegates to the original function, and converts the auto_ptr provided by Boost.Python to the unique_ptr required by the API. The SpamFactory_consume auxiliary function is exposed as SpamFactory.consume():

boost::python::class_<SpamFactory>(...)
// ...
.def("consume", &SpamFactory_consume)
;

Here is a complete code example:

#include <iostream>
#include <memory>
#include <boost/python.hpp>

/// @brief Mockup Spam class.
struct Spam
{
Spam(std::size_t x) : x(x) { std::cout << "Spam()" << std::endl; }
~Spam() { std::cout << "~Spam()" << std::endl; }
Spam(const Spam&) = delete;
Spam& operator=(const Spam&) = delete;
std::size_t x;
};

/// @brief Mockup factor for Spam.
struct SpamFactory
{
/// @brief Create Spam instances.
std::unique_ptr<Spam> make(const std::string& str)
{
return std::unique_ptr<Spam>{new Spam{str.size()}};
}

/// @brief Delete Spam instances.
void consume(std::unique_ptr<Spam>) {}
};

/// @brief Adapter a non-member function that returns a unique_ptr to
/// a python function object that returns a raw pointer but
/// explicitly passes ownership to Python.
template <typename T,
typename ...Args>
boost::python::object adapt_unique(std::unique_ptr<T> (*fn)(Args...))
{
return boost::python::make_function(
[fn](Args... args) { return fn(args...).release(); },
boost::python::return_value_policy<boost::python::manage_new_object>(),
boost::mpl::vector<T*, Args...>()
);
}

/// @brief Adapter a member function that returns a unique_ptr to
/// a python function object that returns a raw pointer but
/// explicitly passes ownership to Python.
template <typename T,
typename C,
typename ...Args>
boost::python::object adapt_unique(std::unique_ptr<T> (C::*fn)(Args...))
{
return boost::python::make_function(
[fn](C& self, Args... args) { return (self.*fn)(args...).release(); },
boost::python::return_value_policy<boost::python::manage_new_object>(),
boost::mpl::vector<T*, C&, Args...>()
);
}

/// @brief Wrapper function for SpamFactory::consume(). This
/// is required because Boost.Python will pass a handle to the
/// Spam instance as an auto_ptr that needs to be converted to
/// convert to a unique_ptr.
void SpamFactory_consume(
SpamFactory& self,
std::auto_ptr<Spam> ptr) // Note auto_ptr provided by Boost.Python.
{
return self.consume(std::unique_ptr<Spam>{ptr.release()});
}

BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::class_<Spam, boost::noncopyable>(
"Spam", python::init<std::size_t>())
.def_readwrite("x", &Spam::x)
;

python::class_<SpamFactory>("SpamFactory", python::init<>())
.def("make", adapt_unique(&SpamFactory::make))
.def("consume", &SpamFactory_consume)
;
}

Interactive Python:

>>> import example
>>> factory = example.SpamFactory()
>>> spam = factory.make("a" * 21)
Spam()
>>> spam.x
21
>>> spam.x *= 2
>>> spam.x
42
>>> factory.consume(spam)
~Spam()
>>> spam.x = 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
None.None(Spam, int)
did not match C++ signature:
None(Spam {lvalue}, unsigned int)

Is there a boost::shared_ptrT equivalent in C#?

boost::shared_ptr allows for reference counted pointers in an environment which is not garbage collected. The .NET runtime allows for full garbage collection, so there is no need for a reference counting pointer container - simply store your object references.



Related Topics



Leave a reply



Submit