I profiled my program, and found that changing from standard allocator to a custom one-frame allocator can remove my biggest bottleneck.
Here is a dummy snippet (coliru link):-
class Allocator{ //can be stack/heap/one-frame allocator
//some complex field and algorithm
//e.g. virtual void* allocate(int amountByte,int align)=0;
//e.g. virtual void deallocate(void* v)=0;
};
template<class T> class MyArray{
//some complex field
Allocator* allo=nullptr;
public: MyArray( Allocator* a){
setAllocator(a);
}
public: void setAllocator( Allocator* a){
allo=a;
}
public: void add(const T& t){
//store "t" in some array
}
//... other functions
};
However, my one-frame allocator has a drawback - user must be sure that every objects allocated by one-frame allocator must be deleted/released at the end of time-step.
Problem
Here is an example of use-case.
I use the one-frame allocator to store temporary result of M3 (overlapping surface from collision detection; wiki link) in Physics Engine.
Here is a snippet.
M1,M2 and M3 are all manifolds, but in different level of detail :-
Allocator oneFrameAllocator;
Allocator heapAllocator;
class M1{}; //e.g. a single-point collision site
class M2{ //e.g. analysed many-point collision site
public: MyArray<M1> m1s{&oneFrameAllocator};
};
class M3{ //e.g. analysed collision surface
public: MyArray<M2> m2s{&oneFrameAllocator};
};
Notice that I set default allocator to be oneFrameAllocator (because it is CPU-saver).
Because I create instance of M1,M2 and M3 only as temporary variables, it works.
Now, I want to cache a new instance of M3 outout_m3=m3; for the next timeStep.
(^ To check whether a collision is just start or just end)
In other words, I want to copy one-frame allocated m3 to heap allocated output_m3 at #3 (shown below).
Here is the game-loop :-
int main(){
M3 output_m3; //must use "heapAllocator"
for(int timeStep=0;timeStep<100;timeStep++){
//v start complex computation #2
M3 m3;
M2 m2;
M1 m1;
m2.m1s.add(m1);
m3.m2s.add(m2);
//^ end complex computation
//output_m3=m3; (change allocator, how? #3)
//.... clean up oneFrameAllocator here ....
}
}
I can't assign output_m3=m3 directly, because output_m3 will copy usage of one-frame allocator from m3.
My poor solution is to create output_m3 from bottom up.
The below code works, but very tedious.
M3 reconstructM3(M3& src,Allocator* allo){
//very ugly here #1
M3 m3New;
m3New.m2s.setAllocator(allo);
for(int n=0;n<src.m2s.size();n++){
M2 m2New;
m2New.m1s.setAllocator(allo);
for(int k=0;k<src.m2s[n].m1s.size();k++){
m2New.m1s.add(src.m2s[n].m1s[k]);
}
m3New.m2s.add(m2New);
}
return m3New;
}
output_m3=reconstructM3(m3,&heapAllocator);
Question
How to switch allocator of an object elegantly (without propagating everything by hand)?
Bounty Description
The answer doesn't need to base on any of my snippet or any Physics thing. My code may be beyond repair.
IMHO, passing type-of-allocator as a class template parameter (e.g. MyArray<T,StackAllocator> ) is undesirable.
I don't mind vtable-cost of Allocator::allocate() and Allocator::deallocate().
I dream for a C++ pattern/tool that can propagate the allocator to members of a class automatically. Perhaps, it is operator=() like MSalters advised, but I can't find a proper way to achieve it.
Reference: After receiving an answer from JaMiT, I found that this question is similar to Using custom allocator for AllocatorAwareContainer data members of a class .
Justification
At its core, this question is asking for a way to use a custom allocator with a multi-level container. There are other stipulations, but after thinking about this, I've decided to ignore some of those stipulations. They seem to be getting in the way of solutions without a good reason. That leaves open the possibility of an answer from the standard library: std::scoped_allocator_adaptor and std::vector.
Perhaps the biggest change with this approach is tossing the idea that a container's allocator needs to be modifiable after construction (toss the setAllocator member). That idea seems questionable in general and incorrect in this specific case. Look at the criteria for deciding which allocator to use:
One-frame allocation requires the object be destroyed by the end of the loop over timeStep.
Heap allocation should be used when one-frame allocation cannot.
That is, you can tell which allocation strategy to use by looking at the scope of the object/variable in question. (Is it inside or outside the loop body?) Scope is known at construction time and does not change (as long as you don't abuse std::move). So the desired allocator is known at construction time and does not change. However, the current constructors do not permit specifying an allocator. That is something to change. Fortunately, such a change is a fairly natural extension of introducing scoped_allocator_adaptor.
The other big change is tossing the MyArray class. Standard containers exist to make your programming easier. Compared to writing your own version, the standard containers are faster to implement (as in, already done) and less prone to error (the standard strives for a higher bar of quality than "works for me this time"). So out with the MyArray template and in with std::vector.
How to do it
The code snippets in this section can be joined into a single source file that compiles. Just skip over my commentary between them. (This is why only the first snippet includes headers.)
Your current Allocator class is a reasonable starting point. It just needs a pair of methods that indicate when two instances are interchangeable (i.e. when both are able to deallocate memory that was allocated by either of them). I also took the liberty of changing amountByte to an unsigned type, since allocating a negative amount of memory does not make sense. (I left the type of align alone though, since there is no indication of what values this would take. Possibly it should be unsigned or an enumeration.)
#include <cstdlib>
#include <functional>
#include <scoped_allocator>
#include <vector>
class Allocator {
public:
virtual void * allocate(std::size_t amountByte, int align)=0;
virtual void deallocate(void * v)=0;
//some complex field and algorithm
// **** Addition ****
// Two objects are considered equal when they are interchangeable at deallocation time.
// There might be a more refined way to define this relation, but without the internals
// of Allocator, I'll go with simply being the same object.
bool operator== (const Allocator & other) const { return this == &other; }
bool operator!= (const Allocator & other) const { return this != &other; }
};
Next up are the two specializations. Their details are outside the scope of the question, though. So I'll just mock up something that will compile (needed since one cannot directly instantiate an abstract base class).
// Mock-up to allow defining the two allocators.
class DerivedAllocator : public Allocator {
public:
void * allocate(std::size_t amountByte, int) override { return std::malloc(amountByte); }
void deallocate(void * v) override { std::free(v); }
};
DerivedAllocator oneFrameAllocator;
DerivedAllocator heapAllocator;
Now we get into the first meaty chunk – adapting Allocator to the standard's expectations. This consists of a wrapper template whose parameter is the type of object being constructed. If you can parse the Allocator requirements, this step is simple. Admitedly, parsing the requirements is not simple since they are designed to cover "fancy pointers".
// Standard interface for the allocator
template <class T>
struct AllocatorOf {
// Some basic definitions:
//Allocator & alloc; // A plain reference is an option if you don't support swapping.
std::reference_wrapper<Allocator> alloc; // Or a pointer if you want to add null checks.
AllocatorOf(Allocator & a) : alloc(a) {} // Note: Implicit conversion allowed
// Maybe this value would come from a helper template? Tough to say, but as long as
// the value depends solely on T, the value can be a static class constant.
static constexpr int ALIGN = 0;
// The things required by the Allocator requirements:
using value_type = T;
// Rebind from other types:
template <class U>
AllocatorOf(const AllocatorOf<U> & other) : alloc(other.alloc) {}
// Pass through to Allocator:
T * allocate (std::size_t n) { return static_cast<T *>(alloc.get().allocate(n * sizeof(T), ALIGN)); }
void deallocate(T * ptr, std::size_t) { alloc.get().deallocate(ptr); }
// Support swapping (helps ease writing a constructor)
using propagate_on_container_swap = std::true_type;
};
// Also need the interchangeability test at this level.
template<class T, class U>
bool operator== (const AllocatorOf<T> & a_t, const AllocatorOf<U> & a_u)
{ return a_t.get().alloc == a_u.get().alloc; }
template<class T, class U>
bool operator!= (const AllocatorOf<T> & a_t, const AllocatorOf<U> & a_u)
{ return a_t.get().alloc != a_u.get().alloc; }
Next up are the manifold classes. The lowest level (M1) does not need any changes.
The mid-levels (M2) need two additions to get the desired results.
The member type allocator_type needs to be defined. Its existence indicates that the class is allocator-aware.
There needs to be a constructor that takes, as parameters, an object to copy and an allocator to use. This makes the class actually allocator-aware. (Potentially other constructors with an allocator parameter would be required, depending on what you actually do with these classes. The scoped_allocator works by automatically appending the allocator to the provided construction parameters. Since the sample code makes copies inside the vectors, a "copy-plus-allocator" constructor is needed.)
In addition, for general use, the mid-levels should get a constructor whose lone parameter is an allocator. For readability, I'll also bring back the MyArray name (but not the template).
The highest level (M3) just needs the constructor taking an allocator. Still, the two type aliases are useful for readability and consistency, so I'll throw them in as well.
class M1{}; //e.g. a single-point collision site
class M2{ //e.g. analysed many-point collision site
public:
using allocator_type = std::scoped_allocator_adaptor<AllocatorOf<M1>>;
using MyArray = std::vector<M1, allocator_type>;
// Default construction still uses oneFrameAllocator, but this can be overridden.
explicit M2(const allocator_type & alloc = oneFrameAllocator) : m1s(alloc) {}
// "Copy" constructor used via scoped_allocator_adaptor
//M2(const M2 & other, const allocator_type & alloc) : m1s(other.m1s, alloc) {}
// You may want to instead delegate to the true copy constructor. This means that
// the m1s array will be copied twice (unless the compiler is able to optimize
// away the first copy). So this would need to be performance tested.
M2(const M2 & other, const allocator_type & alloc) : M2(other)
{
MyArray realloc{other.m1s, alloc};
m1s.swap(realloc); // This is where we need swap support.
}
MyArray m1s;
};
class M3{ //e.g. analysed collision surface
public:
using allocator_type = std::scoped_allocator_adaptor<AllocatorOf<M2>>;
using MyArray = std::vector<M2, allocator_type>;
// Default construction still uses oneFrameAllocator, but this can be overridden.
explicit M3(const allocator_type & alloc = oneFrameAllocator) : m2s(alloc) {}
MyArray m2s;
};
Let's see... two lines added to Allocator (could be reduced to just one), four-ish to M2, three to M3, eliminate the MyArray template, and add the AllocatorOf template. That's not a huge difference. Well, a little more than that count if you want to leverage the auto-generated copy constructor for M2 (but with the benefit of fully supporting the swapping of vectors). Overall, not that drastic a change.
Here is how the code would be used:
int main()
{
M3 output_m3{heapAllocator};
for ( int timeStep = 0; timeStep < 100; timeStep++ ) {
//v start complex computation #2
M3 m3;
M2 m2;
M1 m1;
m2.m1s.push_back(m1); // <-- vector uses push_back() instead of add()
m3.m2s.push_back(m2); // <-- vector uses push_back() instead of add()
//^ end complex computation
output_m3 = m3; // change to heap allocation
//.... clean up oneFrameAllocator here ....
}
}
The assignment seen here preserves the allocation strategy of output_m3 because AllocatorOf does not say to do otherwise. This seems to be what should be the desired behavior, not the old way of copying the allocation strategy. Note that if both sides of an assignment already use the same allocation strategy, it doesn't matter if the strategy is preserved or copied. Hence, existing behavior should be preserved with no need for further changes.
Aside from specifying that one variable uses heap allocation, use of the classes is no messier than it was before. Since it was assumed that at some point there would be a need to specify heap allocation, I don't see why this would be objectionable. Use the standard library – it's there to help.
Since you're aiming at performance, I imply that your classes would not manage the lifetime of allocator itself, and would simply use it's raw pointer. Also, since you're changing storage, copying is inevitable. In this case, all you need is to add a "parametrized copy constructor" to each class, e.g.:
template <typename T> class MyArray {
private:
Allocator& _allocator;
public:
MyArray(Allocator& allocator) : _allocator(allocator) { }
MyArray(MyArray& other, Allocator& allocator) : MyArray(allocator) {
// copy items from "other", passing new allocator to their parametrized copy constructors
}
};
class M1 {
public:
M1(Allocator& allocator) { }
M1(const M1& other, Allocator& allocator) { }
};
class M2 {
public:
MyArray<M1> m1s;
public:
M2(Allocator& allocator) : m1s(allocator) { }
M2(const M2& other, Allocator& allocator) : m1s(other.m1s, allocator) { }
};
This way you can simply do:
M3 stackM3(stackAllocator);
// do processing
M3 heapM3(stackM3, heapAllocator); // or return M3(stackM3, heapAllocator);
to create other-allocator-based copy.
Also, depeding on your actual code structure, you can add some template magic to automate things:
template <typename T> class MX {
public:
MyArray<T> ms;
public:
MX(Allocator& allocator) : ms(allocator) { }
MX(const MX& other, Allocator& allocator) : ms(other.ms, allocator) { }
}
class M2 : public MX<M1> {
public:
using MX<M1>::MX; // inherit constructors
};
class M3 : public MX<M2> {
public:
using MX<M2>::MX; // inherit constructors
};
I realize this isn't the answer to your question - but if you only need the object for the next cycle ( and not future cycles past that ), can you just keep two one-frame allocators destroying them on alternate cycles?
Since you are writing the allocator yourself this could be handled directly in the allocator where the clean-up function knows if this is an even or odd cycle.
Your code would then look something like:
int main(){
M3 output_m3;
for(int timeStep=0;timeStep<100;timeStep++){
oneFrameAllocator.set_to_even(timeStep % 2 == 0);
//v start complex computation #2
M3 m3;
M2 m2;
M1 m1;
m2.m1s.add(m1);
m3.m2s.add(m2);
//^ end complex computation
output_m3=m3;
oneFrameAllocator.cleanup(timestep % 2 == 1); //cleanup odd cycle
}
}
Related
Example: A wrapper for std::vector. I have 2 move constructors:
template <class Allocator>
class MyVector {
....
MyVector(MyVector&&) = default;
MyVector(MyVector&& other, const Allocator<int>& alloc) : vec(std::move(other.vec), alloc) {}
private:
std::vector<int, Allocator<int>> vec;
...
}
However, I want to do an optimization to avoid a costly constructor of vector in case that the given memory allocator is the same as in the moved parameter. Something like:
class MyVector {
MyVector(MyVector&& other, const Allocator<int>& alloc)
: if (other.vec.get_allocator() == alloc)
vec(std::move(other.vec))
else
vec(std::move(other.vec), alloc)
{}
}
Is this even possible in C++?
Note: Question Right way to conditionally initialize a C++ member variable? is not similar, as I cannot push the condition inside the base constructor function call. I need it outside to choose the base constructor.
Context: A third party library code, which I can't change, uses the wrong move constructor (passing allocator when it should not be passed), which I am trying to fix, because it extremely harms the performance.
More context: The problematic code is std::scoped_allocator_adaptor. It treats std::pair as a container, which makes that problem.
Having set<pair<int,MyVector>>, and using 1 scoped allocator for all memory allocations, it generates the wrong constructor in allocator_traits::construct(). The moved MyVector indeed uses the same allocator, but it is obscured by the pair, and the fact that vec.get_allocator() == set.get_allocator() is ignored. So pair construction invokes the move constructor of MyVector with the unnecessary alloc parameter.
Thanks for the comments on the question. Summarizing them as an asnwer.
It seems impossible to use condition to select base constructors.
As a solution I removed the scoped allocator adaptor and just changed the code from MyVector v; v.emplace_back() to v.emplace_back(MyVector::value_type{v.get_allocator()}; thus effectively adding the scoped behaviour by myself.
That bypassed the problematic slow constructor and I indeed measure a considerable gain in speed.
Same changes were done for other containers, like sets
Will not add a sample code as it is specific to my problem and not related to the original question, which was purely about C++ syntax.
I tried to make my custom Vector class, with a template class.
I expect I can put my Vector<int> into a Vector<Vector<int>> variable. At least that was what I was hoping for... but it keeps crashing at the destructor code.
Here's my code.
#include <iostream>
#include <string>
template <typename T>
class Vector {
T* data;
int capacity;
int length;
public:
typedef T value_type;
Vector() {}
Vector(int n) : data(new T[n]), capacity(n), length(0) {}
void push_back(T input) {
data[length++] = input;
}
T operator[](int i) { return data[i]; }
virtual ~Vector() { if (data) delete[] data; }
};
int main() {
Vector<Vector<int>> v(3);
Vector<int> vv(4);
v.push_back(vv);
}
So I thought, maybe I should use a copy constructor, since it seems the problem is that v is being deleted before vv. Well, if I just comment out the destructor code, it will work, but that doesn't seem right to me...
So I made a custom copy constructor like this:
Vector(const T& other) {
}
But it give me an error, saying "ambiguous overloading"... looking back, of course it is wrong, since T of data is different from T of other...
How can I make my custom Vector class work? (i.e. I want push_back work as I intended...)
The general issue
In class design, especially when memory/resource allocation is involved, you typically need to follow the "Rule of Five" (which used to be the "Rule of Three" before C++11):
If you implement any of:
A destructor
An copy/move assignment operator
A copy/move constructor
then you will probably need to implement all of them.
The reason is that each of them likely needs to have some resource management logic, beyond what the language gives you as a default.
The signatures of these five methods, for your class, would be:
Method
Signature
Copy constructor
Vector(const Vector&)
Move constructor
Vector(Vector&&)
Copy assignment operator
Vector& operator=(const Vector&)
Move assignment operator
Vector& operator=(Vector&&)
Destructor
~Vector() or virtual ~Vector()
Your specific class
In your specific case, there are several concrete problems:
As #UnholySheep suggests, you mis-declared the copy constructor.
You implemented a default constructor; but - it doesn't allocate anything, nor does it initialize anything! The data pointer holds arbitrary junk, and when you try to free it, bad things are likely to happen.
You are performing quite a lot of copying of T values, which for the outer vector would be Vector<int> values - and that can get expensive.
Even if you fixed the above, you should still implement the missing methods from the "rule of five".
Your default constructor leaves the object completely uninitialized.
Consider what happens when you declare a
Vector<int> foo;
foo essentially gets a random memory address as data, a random length and capacity. This will give fireworks if you free it.
Perhaps you sidestepped this issue by always creating your vector with a predefined size. Luckily, trying to create/destroy a Vector<Vector<int>> brings this to light, because the Vector<int>[] inside your container still contains these ticking timebombs.
Suppose you have a Container which internally uses other standard containers to form more complex data structures. Thankfully, the standard containers are already designed to do all the necessary work to ensure allocators are copied/assigned etc.
So, normally if we have some Container c, and internally it has an std::vector<int>, we can write a copy-assignment operator that just says:
Container& operator = (const Container& c) { m_vec = c.m_vec; return *this; }
In fact we don't even have to write that (since it's just what the default copy assignment operator does), but let's just say that in this case there's some extra required logic that the default operator wouldn't do:
Container& operator = (const Container& c)
{
/* some other stuff... */
m_vec = c.m_vec;
return *this;
}
So, in this case there's no problem because the vector assignment operator does all the work for us to make sure that allocators are properly copied-on-assignment or not copied.
But... what if we have a vector which we can't simply copy-assign. Suppose it is a vector of pointers to some other internal structure.
Say we have an internal vector which holds pointers: std::vector<node*, Alloc>
So, normally in our copy-assignment operator, we'd have to say:
Container& operator = (const Container& other)
{
vector<node*, Alloc>::allocator_type alloc = m_vec.get_allocator();
for (auto it = m_vec.begin(); it != m_vec.end(); ++it) alloc.deallocate(*it);
m_vec.clear();
for (auto it = other.m_vec.begin(); it != other.m_vec.end(); ++it)
{
node* n = alloc.allocate(1); // this is wrong, we might need to use other.get_allocator() here!
alloc.construct(n, *(*it));
m_vec.push_back(n);
}
return *this;
}
So in the above example, we need to manually deallocate all the node objects in m_vec, and then construct new node objects from the RHS container. (Note that I'm using the same allocator object the vector uses internally in order to allocate node objects.)
But if we want to be standards compliant here and AllocatorAware, we need to check if allocator_traits<std::vector<node*, Alloc>::allocator_type> sets propagate_on_container_copy_assign to true. If it does, we need to use the other container's allocator to construct the copied nodes.
But... our container type Container doesn't use it's own allocator. It just uses an internal std::vector... so how can we tell our internal std::vector instance to use a copied allocator if necessary? The vector doesn't have something like a "use_allocator" or "set_allocator" member function.
So, the only thing I came up with is something like:
if (std::allocator_traits<Alloc>::propagate_on_container_copy_assignment::value)
{
m_vec = std::vector<node*, Alloc>(other.get_allocator());
}
...and then we could construct our nodes with the return value of m_vec.get_allocator();
Is this a valid idiom for creating an allocator aware container that doesn't keep it's own allocator, but rather defers to an internal standard container?
One problem with using swap to implement copy assignment in this example is that if propagate_on_assignment == true_type and propagate_on_container_swap == false_type, then the allocator is not propagated from other to *this, because the swap refuses to do so.
A second problem with this approach is that if both propagate_on_assignment and propagate_on_container_swap == true_type but other.m_vec.get_allocator() != m_vec.get_allocator(), then you do propagate the allocator but you get undefined behavior at the point of swap.
To do this right, you really need to design your operator= from first principals. For this exercise I'm assuming that Container looks something like this:
template <class T, class Alloc>
struct Container
{
using value_type = T;
static_assert(std::is_same<typename Alloc::value_type, value_type>{}, "");
using allocator_type = Alloc;
struct node {};
using NodePtr = typename std::pointer_traits<
typename std::allocator_traits<allocator_type>::pointer>::template
rebind<node>;
using NodePtrAlloc = typename std::allocator_traits<allocator_type>::template
rebind_alloc<NodePtr>;
std::vector<NodePtr, NodePtrAlloc> m_vec;
// ...
I.e. Container is templated on T and Alloc, and that the implementation allows for the possibility that Alloc is using "fancy pointers" (i.e. node* is actually a class type).
In this event, here is what the Container copy assignment operator might look like:
Container&
operator = (const Container& other)
{
if (this != &other)
{
using NodeAlloc = typename std::allocator_traits<NodePtrAlloc>::template
rebind_alloc<node>;
using NodeTraits = std::allocator_traits<NodeAlloc>;
NodeAlloc alloc = m_vec.get_allocator();
for (auto node_ptr : m_vec)
{
NodeTraits::destroy(alloc, std::addressof(*node_ptr));
NodeTraits::deallocate(alloc, node_ptr, 1);
}
if (typename NodeTraits::propagate_on_container_copy_assignment{})
m_vec = other.m_vec;
m_vec.clear();
m_vec.reserve(other.m_vec.size());
NodeAlloc alloc2 = m_vec.get_allocator();
for (auto node_ptr : other.m_vec)
{
using deleter = allocator_deleter<NodeAlloc>;
deleter hold{alloc2, 1};
std::unique_ptr<node, deleter&> n{NodeTraits::allocate(alloc2, 1),
hold};
NodeTraits::construct(alloc2, std::addressof(*n), *node_ptr);
hold.constructed = true;
m_vec.push_back(n.get());
n.release();
}
}
return *this;
}
Explanation:
In order to allocate and deallocate memory with a compatible allocator, we need to use std::allocator_traits to create an "allocator<node>". This is named NodeAlloc in the above example. It is also convenient to form traits for this allocator, termed NodeTraits above.
The first job is to a converted copy of the lhs allocator (converted from allocator<node*> to allocator<node>), and use that allocator to both destroy and deallocate the lhs nodes. std::addressof is needed to convert the possibly "fancy pointer" to an actual node* in the call to destroy.
Next, and this is a bit subtle, we need to propagate m_vec.get_allocator() to m_vec, but only if propagate_on_container_copy_assignment is true. The copy assignment operator of the vector is the best way to do this. This unnecessarily copy some NodePtrs, however I still believe this is the best way to get that allocator propagated. We could also do the vector assignment if propagate_on_container_copy_assignment is false, thus avoiding the if-statement. The assignment would not propagate the allocator if propagate_on_container_copy_assignment is false, but then we still might assign some NodePtrs, when all we really need is a no-op.
If propagate_on_container_copy_assignment is true, and the two allocators are unequal, the vector copy assignment operator will correctly handle dumping the lhs resources for us prior to assigning the allocators. This is a complication that is easy to overlook, and thus best left up to the vector copy assignment operator.
If propagate_on_container_copy_assignment is false, that means we don't need to worry about the case where we have unequal allocators. We aren't going to swap any resources.
In any event, after doing this, we should clear() the lhs. This operation does not dump capacity() and so is not wasteful. At this point we have a zero-sized lhs with the correct allocator, and perhaps even some non-zero capacity() to play with.
As an optimization we can reserve with other.size(), in case the lhs capacity is not sufficient. This line is not necessary for correctness. It is a pure optimization.
Just in case m_vec.get_allocator() might now return a new allocator, we go ahead and get a fresh copy of it, named alloc2 above.
We can now use alloc2 to allocate, construct, and store new nodes which are copy constructed from the rhs.
To be exception safe, we should use an RAII device to hold the allocated pointer while we construct into it, and push_back it into the vector. Either the construction can throw, as can the push_back(). The RAII device must be aware of whether it needs to just deallocate, or both destruct and deallocate in an exception circumstance. The RAII device also needs to be "fancy pointer"-aware. It turns out that it is very easy to build all of this using std::unique_ptr combined with a custom deleter:
template <class Alloc>
class allocator_deleter
{
using traits = std::allocator_traits<Alloc>;
public:
using pointer = typename traits::pointer;
using size_type = typename traits::size_type;
private:
Alloc& alloc_;
size_type s_;
public:
bool constructed = false;
allocator_deleter(Alloc& a, size_type s) noexcept
: alloc_(a)
, s_(s)
{}
void
operator()(pointer p) noexcept
{
if (constructed)
traits::destroy(alloc_, std::addressof(*p));
traits::deallocate(alloc_, p, s_);
}
};
Note the consistent use of std::allocator_traits for all accesses to the allocator. This allows std::allocator_traits to provide defaults so that the author of Alloc need not provide them. For example std::allocator_traits can provide default implementations for construct, destroy, and propagate_on_container_copy_assignment.
Also note the consistent avoidance of the assumption that NodePtr is a node*.
It seems reasonable to leverage existing functionality. Personally, I would go a step further and actually leverage an existing implementation to do the copy. Generally, it seems a suitable copy-and-swap idiom is the easiest approach to implement copy assign:
Container& Container::operator= (Container const& other) {
Container(other,
std::allocator_traits<Alloc>::propagate_on_assignment
? other.get_allocator()
: this->get_allocator()).swap(*this);
return *this;
}
This approach makes a few assumptions, though:
The copy constructor is implemented in a form which [optionally] gets an allocator passed:
Container::Container(Container const& other, Alloc allcoator = Alloc()));
It assumes that the swap() appropriately swaps the allocator.
It is worth pointing out that this approach has the advantage of being reasonably simple and providing a strong exception guarantee but it uses newly allocated memory. If the memory of the LHS object is reused it may result in better performance, e.g., because the used memory is already resonably close to the processor. That is, for an initial implementation I would use the copy and swap implementation (using an extended copy constructor as mentioned above) and replace it by a more involved implementation if profiling indicates that's needed.
How do I initialize an array through a constructor?
Here is the code for a class called Sort:
private:
double unpartitionedList[];
public:
Sort(double unpartitionedList[]);
Sort::Sort(double unpartitionedList[])
{
this->unpartitionedList = unpartitionedList;
}
I'd like to be able to pass an array to the constructor and have it stored in unpartitionedList[]. Like this: Sort(array[])
As the code is now, I get a compiler error in DevC++:
"[Error] incompatible types in assignment of 'double*' to 'double[0]'"
I've tried inserting reference (&) and dereference (*) operators where I thought they were needed, but unfortunately, my attempts were unsuccessful.
Any suggestions would be greatly appreciated.
Arrays aren't assignable. You'll have to do an element-wise copy or write actual C++ code and use std::array or std::vector.
class Sort
{
private:
double unpartitionedList[];
public:
Sort(double unpartitionedList[]);
};
Sort::Sort(double unpartitionedList[])
{
this->unpartitionedList = unpartitionedList;
}
That code will not compile as arrays are not assignable. You can accomplish your goal a few different ways (depending on the requirements you haven't mentioned).
Method 1: Manual Memory Management
class Sort
{
private:
double* unpartitionedList;
std::size_t _size;
public:
Sort(double* upl, std::size_t n);
};
Sort::Sort(double* upl, std::size_t n) : unpartitionedList(upl), _size(n)
{
}
There are a few things to note here: If you intend for this class to take ownership of the memory, you will have to properly manage it (e.g. free the memory in the destructor, and provide a proper copy-constructor that will perform a deep-copy). Because of the memory management requirements, this method is not recommended if not absolutely necessary.
Method 2/3: STD Containers
class Sort
{
private:
std::vector<double> _upl;
// or
// std::array<double, SIZE> upl; // with a constant SIZE defined
public:
Sort(const std::vector<double>& upl);
};
Sort::Sort(const std::vector<double>& upl) : _upl(upl)
// Sort::Sort(const std::array<double, SIZE>& upl) : _upl(upl)
{
}
This will remove the memory management requirement. std::vector will allow you to size the array at runtime. std::array is a thin wrapper around a C-style array (and must be sized at compile time).
Given a class like this:
class Foo
{
const int a;
};
Is it possible to put that class in a vector? When I try, my compiler tells me it can't use the default assignment operator. I try to write my own, but googling around tells me that it's impossible to write an assignment operator for a class with const data members. One post I found said that "if you made [the data member] const that means you don't want assignment to happen in the first place." This makes sense. I've written a class with const data members, and I never intended on using assignment on it, but apparently I need assignment to put it in a vector. Is there a way around this that still preserves const-correctness?
I've written a class with const data members, and I never intended on using assignment on it, but apparently I need assignment to put it in a vector. Is there a way around this that still preserves const-correctness?
You have to ask whether the following constraint still holds
a = b;
/* a is now equivalent to b */
If this constraint is not true for a and b being of type Foo (you have to define the semantics of what "equivalent" means!), then you just cannot put Foo into a Standard container. For example, auto_ptr cannot be put into Standard containers because it violates that requirement.
If you can say about your type that it satisfies this constraint (for example if the const member does not in any way participate to the value of your object, but then consider making it a static data member anyway), then you can write your own assignment operator
class Foo
{
const int a;
public:
Foo &operator=(Foo const& f) {
/* don't assign to "a" */
return *this;
}
};
But think twice!. To me, it looks like that your type does not satisfy the constraint!
Use a vector of pointers std::vector<Foo *>. If you want to avoid the hassle of cleaning up after yourself, use boost::ptr_vector.
Edit: My initial stab during my coffee break, static const int a; won't work for the use case the OP has in mind, which the initial comments confirm, so I'm rewriting and expanding my answer.
Most of the time, when I want to make an element of a class constant, it's a constant whose value is constant for all time and across all instances of the class. In that case, I use a static const variable:
class Foo
{
public:
static const int a;
};
Those don't need to be copied among instances, so if it applied, that would fix your assignment problem. Unfortunately, the OP has indicated that this won't work for the case the OP has in mind.
If you want to create a read-only value that clients can't modify, you can make it a private member variable and only expose it via a const getter method, as another post on this thread indicates:
class Foo
{
public:
int get_a() const { return a; }
private:
int a;
};
The difference between this and
class Foo
{
public:
const int a;
};
is:
The const int gives you assurance that not even the implementation of the class will be able to muck with the value of a during the lifetime of the object. This means that assignment rightfully won't work, since that would be trying to modify the value of a after the object's been created. (This is why, btw, writing a custom operator=() that skips the copy of a is probably a bad idea design-wise.)
The access is different – you have to go through a getter rather than accessing the member directly.
In practice, when choosing between the two, I use read-only members. Doing so probably means you'll be able to replace the value of an object with the value of another object without violating semantics at all. Let's see how it would work in your case.
Consider your Grid object, with a width and height. When you initially create the vector, and let's say you reserve some initial space using vector::reserve(), your vector will be populated with initial default-initialized (i.e. empty) Grids. When you go to assign to a particular position in the vector, or push a Grid onto the end of the vector, you replace the value of the object at that position with a Grid that has actual stuff. But you may be OK with this! If the reason you wanted width and height to be constant is really to ensure consistency between width and height and the rest of the contents of your Grid object, and you've verified that it doesn't matter whether width and height are replaced before or after other elements of Grid are replaced, then this assignment should be safe because by the end of the assignment, the entire contents of the instance will have been replaced and you'll be back in a consistent state. (If the lack of atomicity of the default assignment was a problem, you could probably get around this by implementing your own assignment operator which used a copy constructor and a swap() operation.)
In summary, what you gain by using read-only getters is the ability to use the objects in a vector or any container with value semantics. However, it then falls to you to ensure that none of Grid's internal operations (or the operations of friends of Grid) violate this consistency, because the compiler won't be locking down the width and height for you. This goes for default construction, copy construction, and assignment as well.
I'm considering making the data member non-const, but private and only accessible by a get function, like this:
class Foo
{
private:
int a;
public:
int getA() const {return a;}
};
Is this 'as good' as const? Does it have any disadvantages?
As of c++20, using const member variables are legal without restrictions that had made it virtually unusable in containers. You still have to define a copy assignment member function because it continues to be automatically deleted when a const object exists in the class. However, changes to "basic.life" now allow changing const sub-objects and c++ provides rather convenient functions for doing this. Here's a description of why the change was made:
The following code shows how to define a copy assignment member function which is useable in any class containing const member objects and uses the new functions std::destroy_at and std::construct_at to fulfil the requirement so the new "basic.life" rules. The code demonstrates assignment of vectors as well as sorting vectors with const elements.
Compiler explorer using MSVC, GCC, CLANG https://godbolt.org/z/McfcaMWqj
#include <memory>
#include <vector>
#include <iostream>
#include <algorithm>
class Foo
{
public:
const int a;
Foo& operator=(const Foo& arg) {
if (this != &arg)
{
std::destroy_at(this);
std::construct_at(this, arg);
}
return *this;
}
};
int main()
{
std::vector<Foo> v;
v.push_back({ 2 });
v.push_back({ 1 });
v.insert(v.begin() + 1, Foo{ 0 });
std::vector<Foo> v2;
v2 = v;
std::sort(v2.begin(), v2.end(), [](auto p1, auto p2) {return p1.a < p2.a; });
for (auto& x : v2)
std::cout << x.a << '\n';
}