For a user-defined allocator, the relation between the allocated-units must be constructed at the beginning, while the memory space for elements should be left uninitialized.
A simple demo:
template <typename T>
struct Node {
T _item;
Node* _prev;
Node* _next;
};
template <typename T, typename ParentAllocator>
class MyAllocator {
using Allocator = std::allocator_traits<ParentAllocator>::rebind_alloc<Node<T>>;
Allocator _allocator;
Node<T> _header;
/* ... */
public:
MyAllocator()
: _allocator()
{
_header._prev = &_header;
_header._next = &_header;
// leaving `_item` uninitialized
}
T* allocate(/*one*/) {
auto* newNode = _allocator.allocate(1);
newNode->_prev = &_header;
newNode->_next = _header._next;
// leaving `_item` uninitialized
/* ... */
return &(newNode->_item);
}
};
Node is not initialized, instead direct initialization for its members, though not for all.
My questions are:
Are _header and _next really partially initialized as expectations, even if the default constructor of T (both normal and explicit one) were deleted.
Have I implemented it properly?
If not, what's the right way?
You need to modify Node to make it default constructible, and you don't want to default construct T even if it has a default constructor. So you can replace T _item with:
std::aligned_storage<sizeof(T), alignof(T)> _item;
Or in C++23 because std::aligned_storage is deprecated:
alignas(T) std::byte _item[sizeof(T)];
That will give you the storage space you need, with appropriate alignment, and then you'll use placement new to construct T in that storage. You will also need to explicitly invoke ~T() before or during destruction of each Node.
Demo showing the basics, certainly not complete or tested: https://godbolt.org/z/bGaKWb3de
Related
I have a couple of problems in my code. The first is in the variable "Element" and it works well for me as long as the constructor of the class that sent the template its variables have default values, is there a way to skip the constructor without putting defaults values in the class? And the other problem is when it comes to freeing memory, when T is of the pointer type I will need to do the delete but just as I put the code I get an error, is there any other solution that can help me? I will be attentive to your answers, thanks: D
namespace Linked{
template <class T>
struct Nodo{
const bool isponter = is_pointer<T>::value;
T Element;
Nodo<T> *Next;
Nodo(){
this->Next = nullptr;
}
~Nodo(){
if(is_pointer<T>::value)
delete Element;
}
};
}
namespace Linked{
template <class T>
struct Nodo{
T Element;
Nodo<T> *Next = nullptr;
~Nodo(){
if constexpr (std::is_pointer<T>::value)
delete Element;
}
};
You should also consider if T is pointer to array.
The only syntactial problems with your code are, that you didn't #include <type_traits> and forgot the std:: before is_pointer<T>::value.
But what you are attempting to do will raise problems with ownership. When Nodo contains a pointer it shouldn't own the object that pointer is pointing to. So you can't just delete that pointer, since you can't even know where it is pointing. Consider the following three cases, each of them requires different handling, but you have no way of determining what case you are facing:
Nodo<int*> n1, n2, n3;
n1.Element = new int(1); // requires delete
n2.Element = new int[10]; // requires delete[], crashes with delete
int i = 0;
n3.Element = &i; // no delete at all, crashes with delete
Usually whoever allocated a object on the heap is responsible for deallocating it. Nodo should not attempt to deallocate memory which it hasn't allocated.
As you didn't specify a c++ version, I'm gonna assume you use the latest, which now is C++17. The closest fit to your existing code is using if constexpr, I won't elaborate on that as there are other good answers for that. If you are stuck on C++14 or C++11 (or worse 03/98, in which case you should simply upgrade), you will need to specialize your template. (I'll come back to this)
This code however, is violating one of the CppCoreGuidelines: ES.24: Use a unique_ptr<T> to hold pointers By writing your template to detect raw pointers and delete it, one always has to allocate. Hence your linked list can't refer to some sub-data of something existing. As already eluded to in the comments, if the users want the memory to be cleaned, use std::unique_ptr. An example:
namespace Linked{
template <class T>
struct Nodo{
T Element;
Nodo<T> *Next{nullptr};
Nodo() = default;
~Nodo() = default;
};
}
// Has ownership
auto node = Nodo<std::unique_ptr<int>>{};
node.element = std::make_unique<int>(42);
// Has ownership (to array of 42 elements)
auto node = Nodo<std::unique_ptr<int[]>>{};
node.element = std::make_unique<int[]>(42);
// No ownership
int value = 42;
auto node = Nodo<int>{};
node.element = &value;
With this, ownership is clear to the caller and transparant for you. (as you don't need to know about arrays, std::unique_ptr knows about that) You might want to put some restrictions on T, like adding static_assert(std::is_nothrow_move_constructable<T>);.
This above solution solves the problem in C++11 and upwards and should be the recommended approach.
If not, use if constexpr if your condition isn't capturable in a dedicated class in C++17. And partial specialization in C++14 and C++11.
namespace Linked{
template <class T>
struct Nodo{
T Element;
Nodo<T> *Next{nullptr};
Nodo() = default;
~Nodo() = default;
};
template <class T>
struct Nodo<T*>{
T *Element{nullptr};
Nodo<T> *Next{nullptr};
Nodo() = default;
~Nodo() { delete Element; }
};
}
If you don't want to repeat your code too much
namespace Linked{
template <class T, class Me>
struct AbstractNodo{
T Element;
Me *Next{nullptr};
// All common code
};
template <class T>
struct Nodo : AbstractNodo<T, Nodo<T>>{
Nodo() = default;
~Nodo() = default;
};
template <class T>
struct Nodo<T*> : AbstractNodo<T, Nodo<T*>>{
Nodo() = default;
~Nodo() { delete Element; }
};
}
There is also a way to specialize a single method, however, I'm not that familiar with it, see Stack overflow: Template specialization of a single method from a templated class for more details.
I am making a Node struct and would like to make a default constructor that initializes each member variable.
struct Node {
Node* next;
Node* prev;
T datum;
Node()
{
next = nullptr;
prev = nullptr;
datum = ??????
}
};
Since datum is of type T, which is a template type, what can I set datum equal to here? Setting it to NULL is giving me a compiler error.
EDIT: To clarify, I want to initialize the variables so I can modify them later in my code.
I would do:
struct Node {
Node* next = nullptr;
Node* prev = nullptr;
T datum{};
Node() = default;
};
I figured it out! If you write the default constructor as
Node(){}
initialization will be handled automatically.
The best you can do is:
Node() : next(nullptr), prev(nullptr), datum()
{
}
Please note that it will fail if T is not default constructible.
If T is some class and it has default constructor declared, then you don't have to do anything at all. The member will be just initialized by default.
If you need to call specific constructor, there's quite a lot of possibilities:
first, you can just set initializer at member declaration. I.e. T datum { }; or T datum { 1 /*, other args*/ };. That is it;
second, you can provide initialization in constructor. Like Node() : datum { /*blah-blah */ } { /* the body */ }. Just don't forget to initialize next and prev as well;
third, you can use T just as if it's type. I.e. you can write something like datum = T(/* args go here */); or datum = T { /* yeah, here too */ }; in your constructor. Little note is to be made however: it's not initialization, it's assignment after initialization. The initialization is to be done through first two options;
A complete example:
struct Node {
Node* next;
Node* prev;
T datum { 0 }; // for all constructors
Node() : datum { 1 } // for this particular constructor
{
next = nullptr;
prev = nullptr;
datum = { 2 }; // assign new value now
}
};
Not sure if you need the following, but just in case (and for future generations :) ).
If classes that are going to be used as T don't have viable constructors, or they have different arguments, or something else prevents you from using one initializer for them all...
You can oblige those classes to have static field like default_value and copy constructors. Then you can initialize like that: T datum = T::default_value;. The static field can be replaced with variable template.
Or if you really need to, you can define something like a proxy. Now that we have copy ellision you can define function template that gives you the desired value.
It will look like this:
template <typename T>
T default_value() { return { /* very default arguments */ }; }
// or if you don't need that very default body:
// T default_value();
template <>
A default_value<A>() { return { /* A constructor arguments */ }; }
template <>
B default_value<B>() { return { /* B constructor arguments */ }; }
So in Node you can declare member like T datum = default_value<T>();. In C++17 it doesn't require class to have copy constructor or something else, you have only to construct it somehow in default_value function's return statement, when you already know the specific class. So no modification of the class is to be done. And yes, that function can have arguments, which you can provide in Node() constructor;
I am trying to implement a class that represents a doubly-linked list, and I have a function createNode() which returns a new Node (A templated class) with all its members initialized. This function is going to be used to create linked lists where the size is known, but no data has been passed to it. For most data types, this works. However, this does not work for classes without default constructors, since they cannot be initialized without parameters. Here is the minimal code that exhibits this:
class Test // A class without a default constructor
{
public:
Test(int value) : value_{ value } { };
private:
int value_;
};
template<typename T>
struct Node
{
Node* prev;
Node* next;
T value;
};
template<typename T>
Node<T>* createNode()
{
return new Node<T>{ nullptr, nullptr, T() }; // How do I change T() so
// that I can use classes
// without default constructors?
}
int main()
{
Node<Test>* testNode = createNode<Test>();
delete testNode;
}
Basically, my final goal is to be able to create a linked list which can hold uninitialized nodes while keeping track of which nodes are initialized or not. I remember reading in an old textbook of mine about a method for solving this problem that involves using allocators (Which are used for handling construction/destruction of objects), but I don't remember the exact technique at all. So how should I go about this?
Use std::optional<T> if you have access to C++17, or boost::optional<T> if you don't.
template<typename T>
struct Node
{
Node* prev;
Node* next;
std::optional<T> value; // or boost::optional<T> value;
};
template<typename T>
Node<T>* createNode()
{
return new Node<T>{ nullptr, nullptr, std::nullopt /* or boost::none */ };
}
If you don't have access to C++17 and don't want to include boost, you could roll your own optional template with something like this:
struct nullopt_t {};
nullopt_t nullopt;
template <typename T>
class optional
{
public:
template <typename... Args>
optional(Args&&... args)
: ptr{new ((void*)&storage) T(std::forward<Args>(args)...)}
{}
optional(nullopt_t)
: ptr{nullptr}
{}
~optional()
{
if (ptr) {
ptr->~T();
}
}
optional& operator=(T obj)
{
if (ptr) {
*ptr = std::move(obj);
} else {
ptr = new ((void*)&storage) T(std::move(obj));
}
return *this;
}
explicit operator bool()
{
return ptr != nullptr;
}
T& value()
{
if (!ptr) {
throw std::exception();
}
return *ptr;
}
// Other const-correct and rvalue-correct accessors left
// as an exercise to the reader
private:
std::aligned_storage_t<sizeof(T), alignof(T)> storage;
T* ptr;
};
Live Demo
You can use placement new to place the object later in a pre-allocated memory.
It's just about splitting the memory allocation from the construction of the objects. So you can declare a member in your Node that takes memory but do not construct object because it needs parameter. Later you can construct the object with the needed parameters but not allocate memory with new but use placement new to just call the constructor with memory already allocated within the Node.
So following is an example of a self-made std::optional. In n3527 you can find more details about std::optional.
#include <vector>
#include <functional>
#include <iostream>
#include <algorithm>
#include <string>
#include <memory>
using namespace std;
class Test // A class without a default constructor
{
public:
Test(int value) : value_{ value } { };
//private:
int value_;
};
template<typename T>
struct Node
{
Node* prev;
Node* next;
bool empty = true;
union {
T t;
} value; // Could be replaced with typename std::aligned_storage<sizeof(T), alignof(T)>::type value;
// need a constructor that inits the value union and activate a field
// Node()
~Node() {
if (!empty) {
value.t.~T();
}
}
template<typename... Args>
void setValue(Args... args) {
if (!empty) {
value.t.~T();
}
new (&value.t) T(std::forward<Args...>(args...));
empty = false;
}
T& getValue() {
// TODO:
if (empty) {
//throw
}
return value.t;
}
};
template<typename T>
Node<T>* createNode()
{
return new Node<T>{ nullptr, nullptr }; // How do I change T() so
// that I can use classes
// without default constructors?
}
int main()
{
Node<Test>* testNode = createNode<Test>();
testNode->setValue(42);
if (!testNode->empty) {
std::cout << testNode->getValue().value_;
}
delete testNode;
return 0;
}
Live Demo
With few small changes and with reinterpret_cass you can also use typename std::aligned_storage<sizeof(T), alignof(T)>::type value; - Live Demo
Allocators manage the memory and you will not be able include (aggregate) the object in your class and have to use pointers and second allocation except you use allocator to place the entire Node.
There are interesting presentation form John Lakos about allocators on YouTube - CppCon 2017 Local 'Arena' Memory Allocators part 1 and 2.
What you are asking is literally impossible -- to default construct an object without a default constructor.
Perhaps consider adding a T nodeValue parameter to createNode()? Or change the Node itself so that rather than holding an object, it holds a pointer to the object. That seems like a memory management nightmare, but it could work.
How would you perform a move operation on a class that uses unique_ptr? Wouldn't setting the unique_ptr to null cause deletion of the data? If I perform a copy through a list initializer of the unique_ptr like so, would the data be preserved or deleted?
template<typename T, typename A = std::allocator<T>>
class forward_list
{
...
private:
struct node
{
T data;
std::unique_ptr<T> next;
};
std::unique_ptr<node> root_;
std::unique_ptr<node> leaf_;
size_t count_;
const A& heap;
};
// Move constructor. Constructs the container with the contents of other using move semantics.
// If alloc is not provided, allocator is obtained by move-construction from the allocator belonging to other.
inline forward_list(forward_list&& other)
: root_(other.root_), leaf_(other.leaf_), count_(other.count_), heap(other.heap)
{
other.root_ = nullptr;
other.leaf_ = nullptr;
other.count_ = 0;
};
You need to move the pointer.
forward_list(forward_list&& other) :
root_(std::move(other.root_)),
leaf_(std::move(other.leaf_)),
count_(other.count_),
heap(other.heap)
{
// Do nothing
}
template <class T>
class ListNode {
public:
T* data;
ListNode<T>* next;
}
Lets say I have got a list node template and somewhere in the code I want to get a copy of the data - meaning not a copy of a pointer to data (T*) but a new pointer (T*) which will point to another place in the memory which have the same information there.
How can I do it when using C++ templates? How can I copy (*data) if I don't know what is the type of T.
The compiler knows the type of T. What is does not know is how many instances of T are pointed to. In terms of getting a practical implementation, the short answer would be don't use pointer types. Use containers instead. Since you are copying the node data anyway the overhead is minimal. Explicit example below:
template <class T>
class ListNode {
public:
// use a vector as the container
std::vector<T> data;
ListNode<T>* next;
// initializer from pointer primitive
ListNode(const T* ps,size_t elements)
{
data.assign(ps,ps+elements);
}
// copy templated instance
ListNode(const ListNode& arg)
{
data = arg.data;
}
// assignment
ListNode& operator=(const ListNode& arg)
{
if (this != &arg)
{
data = arg.data;
}
return *this;
}
};
Actual usage would be similar to this:
{
const char* ps = "Hello World";
ListNode<char> ln1(ps,strlen(ps));
ListNode<char> ln2 = ln1;
}
You can, of course, get much more complicated solutions but they will all involve keeping track of the number of instances of type T to which your pointer points.
T has to be copy-constructable so that you can do
template <class T>
ListNode<T>::ListNode(const ListNode<T>& src)
{
...
// given a preexisting copy, src, create a new T to
// perform a copy
T* destT = new T(*srcT);
}
If T has a copy constructor, this will work. If it doesn't, the compiler will give you an error (probably a very cryptic one)
Use operator= or the copy constructor. It's standard practice that both of these will produce a copy of the object.
So, for example:
T *p = other_pointer;
*p = *data;
Or
T* copy = new T(*data);
When creating a copy of a templated type, practically you need not worry about the type as such and live that task on the copy constructor or assignment operator of that type:
template <class T>
class ListNode {
public:
T* data;
ListNode<T>* next;
T* getCopy() { new T(*data); } // get copy of "data"
};
Suppose you use this ListNode<T> for class A, then you can have a copy constructor defined for A (and assignment operator as well):
class A {
public:
A(const A& copy);
};
Now when ListNode<T>::getCopy() is called, it will call the copy constructor of A internally.