I'm working on my own smart pointer and I ran into some weird problems. The move assignment operator was not being called. So I wrote a test class and was able to reproduce the issue. The move assignment operator is not called but a copy assignment occurs (even when there is no copy assignment operator).
This is my test class
#include <utility>
#include <iostream>
struct tag_t {};
constexpr tag_t tag {};
template <typename T>
struct Foo {
Foo() noexcept
: val{} {
std::cout << "Default construct\n";
}
template <typename U>
Foo(tag_t, const U &val) noexcept
: val{val} {
std::cout << "Construct " << val << '\n';
}
~Foo() noexcept {
std::cout << "Destruct " << val << '\n';
}
template <typename U>
Foo(Foo<U> &&other) noexcept
: val{std::exchange(other.val, U{})} {
std::cout << "Move construct " << val << '\n';
}
template <typename U>
Foo &operator=(Foo<U> &&other) noexcept {
std::cout << "Move assign " << other.val << '\n';
val = std::exchange(other.val, U{});
return *this;
}
T val;
};
These are the tests
int main() {
{
Foo<int> num;
std::cout << "Value " << num.val << '\n';
num = {tag, 5};
std::cout << "Value " << num.val << '\n';
}
std::cout << '\n';
{
Foo<int> num;
std::cout << "Value " << num.val << '\n';
num = Foo<int>{tag, 5};
std::cout << "Value " << num.val << '\n';
}
return 0;
}
After running the tests, I get these results
Default construct
Value 0
Construct 5
Destruct 5
Value 5
Destruct 5
Default construct
Value 0
Construct 5
Move assign 5
Destruct 0
Value 5
Destruct 5
What baffles me is the output of the first test. The move assignment operator is not called but a copy assignment takes place. This results in 5 being destroyed twice. Not ideal when you're trying to make a smart pointer!
I'm compiling with Apple Clang with optimizations disabled. Can someone explain my observations? Also, how do I ensure that the move assignment operator is called in the first test?
template <typename U>
Foo &operator=(Foo<U> &&other) noexcept;
this cannot be called by ={ }.
Instead, Foo& operator=(Foo&&)noexcept is called.
Template methods are never special member functions. Explicitly default, delete or implement them.
Related
I'm running into an issue where under a specific set of conditions, a local variable being returning by a function is copied instead of moved. So far, it seems like it needs to meet the following:
The returned variable has some usage in the function. I'm assuming otherwise the whole copy/move is ellided.
The returned type is using a perfect forwarding-style constructor. From this answer (Usage of std::forward vs std::move) I learned that this style of constructor has some different deduction rules.
Be compiled in gcc before 8.0. Compiling under clang (and apparently gcc 8.0+, thanks PaulMcKenzie for the comment) produces the results I expect, however I'm not free to change the compiler being used in the larger project.
Here's some minimum reproduction:
#include <iostream>
#include <type_traits>
// Test class to print copies vs. moves.
class Value
{
public:
Value() : x(0) {}
Value(Value&& other) : x(other.x)
{
std::cout << "value move" << std::endl;
}
Value(const Value& other) : x(other.x)
{
std::cout << "value copy" << std::endl;
}
int x;
};
// A container class using a separate lvalue and rvalue conversion constructor.
template<typename T>
class A
{
public:
A(const T& v) : data_(v)
{
std::cout << "lvalue conversion" << std::endl;
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
A(T&& v) : data_(std::move(v))
{
std::cout << "rvalue conversion" << std::endl;
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
T data_;
};
// A container class using a single perfect forwarding constructor.
template<typename T>
class B
{
public:
template <typename U>
B(U&& v) : data_(std::forward<U>(v))
{
std::cout << "template conversion" << std::endl;
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
T data_;
};
// Get a Value rvalue.
Value get_v()
{
Value v;
v.x = 10; // Without this things get ellided.
return v;
}
// Get an A<Value> rvalue.
A<Value> get_a()
{
Value v;
v.x = 10; // Without this things get ellided.
return v;
}
// Get a B<Value> rvalue.
B<Value> get_b()
{
Value v;
v.x = 10; // Without this things get ellided.
return v;
}
int main()
{
Value v = Value();
std::cout << "--------\nA" << std::endl;
std::cout << "lvalue" << std::endl;
A<Value> a0(v);
std::cout << a0.data_.x << std::endl;
std::cout << "rvalue" << std::endl;
A<Value> a1(get_v());
std::cout << a1.data_.x << std::endl;
std::cout << "get_a()" << std::endl;
std::cout << get_a().data_.x << std::endl;
std::cout << "--------\nB" << std::endl;
std::cout << "lvalue" << std::endl;
B<Value> b0(v);
std::cout << b0.data_.x << std::endl;
std::cout << "rvalue" << std::endl;
B<Value> b1(get_v());
std::cout << b1.data_.x << std::endl;
std::cout << "get_b()" << std::endl;
std::cout << get_b().data_.x << std::endl;
return 0;
}
Under gcc this produces:
--------
A
lvalue
value copy
lvalue conversion
A<T>::A(const T&) [with T = Value]
0
rvalue
value move
rvalue conversion
A<T>::A(T&&) [with T = Value]
10
get_a()
value move <---- Works with separate constructors.
rvalue conversion
A<T>::A(T&&) [with T = Value]
10
--------
B
lvalue
value copy
template conversion
B<T>::B(U&&) [with U = Value&; T = Value]
0
rvalue
value move
template conversion
B<T>::B(U&&) [with U = Value; T = Value]
10
get_b()
value copy <---- Not what I expect!
template conversion
B<T>::B(U&&) [with U = Value&; T = Value]
10
For completeness, clang gives:
--------
A
lvalue
value copy
lvalue conversion
A<Value>::A(const T &) [T = Value]
0
rvalue
value move
rvalue conversion
A<Value>::A(T &&) [T = Value]
10
get_a()
value move
rvalue conversion
A<Value>::A(T &&) [T = Value]
10
--------
B
lvalue
value copy
template conversion
B<Value>::B(U &&) [T = Value, U = Value &]
0
rvalue
value move
template conversion
B<Value>::B(U &&) [T = Value, U = Value]
10
get_b()
value move <---- Like this!
template conversion
B<Value>::B(U &&) [T = Value, U = Value]
10
I have two questions:
Is this allowed behavior by gcc?
Is there any way to force move behavior here through changing the implementation of A/B? It seems any time you have a templated function parameter being taken as && it will trigger the special rules for perfect forwarding, so if I try to provide two constructors as in the A example, one taking const U& and one taking U&&, it won't avoid the problem as long as they have other templating in place.
I have a variadic template with a variadic std::tuple as a member.
I declared the default assignment operator.
MSVC complains that the operator is deleted. I can still run the program.
It doesnt give the expected result.
So 3 Questions come to my mind:
Why is the default copy assignment operator not generated even if flagged as default?
Why can I run the programm still with the code posted?
How to declare a assignment operator for std::tuple<T...> template?
#include <string>
#include <iostream>
#include <tuple>
template<typename ...T>
class Variadic
{
public:
explicit Variadic(T... args)
:m_data{ std::move(args)... }
{
}
Variadic& operator=(const Variadic&) = default;
template<typename Type>
Type get_element() const
{
return std::get<Type>(m_data);
}
private:
std::tuple<T...> m_data{};
};
int main()
{
Variadic<int, std::string> tuple1{ 1, "a" };
Variadic<int, std::string> tuple2{ 2, "b" };
std::cout << tuple1.get_element<int>() << '\n'; // 1
std::cout << tuple1.get_element<std::string>() << '\n'; // a
std::cout << tuple2.get_element<int>() << '\n'; // 2
std::cout << tuple2.get_element<std::string>() << '\n'; // b
tuple2 = tuple1; // MSVC2017 gives warning here:
//function "Variadic<T...>::operator=(const Variadic<T...> &) [with T=<int, std::string>]"
//(declared at line 13) cannot be referenced -- it is a deleted function
// still it compiles ???
std::cin.get();
std::cout << tuple1.get_element<int>() << '\n'; // 1
std::cout << tuple1.get_element<std::string>() << '\n'; // a
std::cout << tuple2.get_element<int>() << '\n'; // expect 1 but 2 ?
std::cout << tuple2.get_element<std::string>() << '\n'; // expect b but a ?
}
I have a trivial allocator:
// alloc.h
#include <cstdlib>
#include <new>
#include <iostream>
template <class T>
struct Mallocator {
typedef T value_type;
Mallocator() {
std::cout << "default ctor is called" << std::endl;
}
template <class U> Mallocator(const Mallocator<U>&) {
std::cout << "copy ctor is called" << std::endl;
}
T* allocate(std::size_t n) {
std::cout << "Mallocator::allocate(size_t n) is called, n = " << n << " ";
if(n > std::size_t(-1) / sizeof(T)) throw std::bad_alloc();
if(T *p = static_cast<T*>(std::malloc(n*sizeof(T)))) {
std::cout << "return p = " << std::hex << (uintptr_t)p << std::dec << std::endl;
return p;
}
throw std::bad_alloc();
}
void deallocate(T* p, std::size_t n) {
std::cout << "Mallocator::deallocate(T *p, size_t n) is called, p = " << std::hex << (uintptr_t)p << std::dec << " n = " << n << std::endl;
std::free(p);
}
};
template <class T, class U>
bool operator==(const Mallocator<T>&, const Mallocator<U>&) { return true; }
template <class T, class U>
bool operator!=(const Mallocator<T>&, const Mallocator<U>&) { return false; }
And this is the client code (only one of A, B, C is used):
#include "alloc.h"
#include <vector>
#include <iostream>
using namespace std;
int main() {
Mallocator<int> a;
cout << "---instantiate---" << endl;
// vector<int, Mallocator<int>> v(a); // A
vector<int, Mallocator<int>> v{Mallocator<int>(a)}; // B
// vector<int, Mallocator<int>> v(Mallocator<int>(a)); // C
cout << "---push_back(1)---" << endl;
v.push_back(1);
cout << "---push_back(2)---" << endl;
v.push_back(2);
cout << "---push_back(3)---" << endl;
v.push_back(3);
cout << "---push_back(4)---" << endl;
v.push_back(4);
cout << "---push_back(5)---" << endl;
v.push_back(5);
cout << "---exiting---" << endl;
}
The output, no matter A or B is used, is always this:
default ctor is called
---instantiate---
---push_back(1)---
// omitted for brevity..
My question:
(1) if A is present, the allocator is just constructed once, that's understandable. But when B is present instead of A, apparently the copy constructor of Mallocator is called in B, but the output doesn't reflect this. Why?
(2) If B is present, which constructor of std::vector is called? In this reference, the only constructor that takes an initializer list doesn't look like this. And if I use C instead of B, it won't compile, and the error message of clang++ is not helping..
Eidt: I know this allocator is trivial but it is not the point of this question..
The code of "alloc.h" is adapted from here, at the end of the page.
1) Your "copy constructor" isn't one. A real copy constructor isn't a template. Every class gets a copy constructor implicitly declared if it doesn't declare one itself. Mallocator<int> doesn't declare a real copy constructor, so one gets implicitly declared and defined for you. Since your class is empty, that copy constructor does nothing and prints nothing (and, thanks to the overload resolution rules, is selected to copy the allocator over your constructor template).
2) List-initialization can call non-initializer-list constructors if no initializer-list constructor is viable. B ends up calling the same constructor as A. Your C is a case of the most-vexing-parse.
I just made a wrapper for move and copy operations to inject into code to see which is called in case of default implementations. I'm getting close to understanding when what is called but would like to double check at times.
I'm not sure if method 1 of using T::T; is better for the constructors than method 2 of forwarding the arguments like unique_ptr? I found it in this thread Forwarding all constructors in C++0x
In move constructor and assignment I use std::move to pass onto the super class. Should this be std::forward and if so, how? I get errors trying to use it.
#ifndef MOVECOPY_OBSERVER_H
#define MOVECOPY_OBSERVER_H
#include <iostream>
template<class T>
class MoveCopyObserver : public T {
public:
//1: Use "using" for constructors
//From https://stackoverflow.com/questions/3119929/forwarding-all-constructors-in-c0x
using T::T;
//2: Forward all args, unique_ptr style.
/*
template<typename... Args>
MoveCopyObserver(Args&&... args)
: T(std::forward<Args>(args)...)
{
};*/
// *************************************************************************
virtual ~MoveCopyObserver() = default;
// *************************************************************************
MoveCopyObserver(const MoveCopyObserver& other)
: T(other)
{
std::cout << "Copy constructor " << typeid(T).name() << std::endl;
}
// *************************************************************************
MoveCopyObserver(MoveCopyObserver && other)
: T(std::move(other)) //3: std::forward instead?
{
std::cout << "Move constructor " << typeid(T).name() << std::endl;
}
// *************************************************************************
MoveCopyObserver& operator=(const MoveCopyObserver& other)
{
T::operator=(other);
std::cout << "Copy assignment " << typeid(T).name() << std::endl;
return *this;
}
// *************************************************************************
MoveCopyObserver& operator=(MoveCopyObserver&& other)
{
T::operator=(std::move(other)); //3: std::forward instead?
std::cout << "Move assignment " << typeid(T).name() << std::endl;
return *this;
}
};
#endif //MOVECOPY_OBSERVER_H
The usage would be on the stack or through smart pointers, like so:
class A {
public:
A(std::string ss)
{
s = ss;
}
void f()
{
std::cout << "\"" << s << "\"" << std::endl;
}
private:
std::string s;
};
A a("Test instance");
a.foo();
MoveCopyObserver<A> b("Another instance");
b.foo();
Hi I created a class Foo with a noexcept move constructor using gcc 4.7 and set the vector reserve size to 2 so that it would have to reallocate the size when adding the 3rd item. It seems it is calling the copy constructor instead of the move constructor when doing this. Am I missing something here?
#include <vector>
#include <iostream>
class Foo
{
public:
Foo(int x) : data_(x)
{
std::cout << " constructing " << std::endl;
}
~Foo()
{
std::cout << " destructing " << std::endl;
}
Foo& operator=(const Foo&) = default;
Foo& operator=(Foo&&) = default;
Foo(Foo&& other) noexcept : data_(std::move(other.data_))
{
std::cout << " Move constructing " << std::endl;
}
Foo(const Foo& other) noexcept : data_(other.data_)
{
std::cout << " Copy constructing " << std::endl;
}
private:
int data_;
};
int main ( int argc, char *argv[])
{
std::vector<Foo> v;
v.reserve(2);
v.emplace_back(1);
std::cout << "Added 1" << std::endl;
v.emplace_back(2);
std::cout << "Added 2" << std::endl;
v.emplace_back(3);
std::cout << "Added 3" << std::endl;
std::cout << "v size: " << v.size() << std::endl;
}
output:
constructing
Added 1
constructing
Added 2
constructing
Copy constructing
Copy constructing
destructing
destructing
Added 3
v size: 3
destructing
destructing
destructing
After tinkering with it a bit with both GCC 4.7 and 4.8, it seems that it is indeed a bug in 4.7, which only appears when the class' destructor is not marked noexcept:
struct Foo {
Foo() {}
~Foo() noexcept {}
Foo(Foo&&) noexcept { std::cout << "move constructor" << std::endl; }
Foo(const Foo&) noexcept { std::cout << "copy constructor" << std::endl; }
};
int main() {
std::vector<Foo> v;
v.reserve(2);
v.emplace_back();
v.emplace_back();
v.emplace_back();
}
GCC 4.7 displays:
move constructor
move constructor
If we remove noexcept from the destructor:
struct Foo {
Foo() {}
~Foo() {}
Foo(Foo&&) noexcept { std::cout << "move constructor" << std::endl; }
Foo(const Foo&) noexcept { std::cout << "copy constructor" << std::endl; }
};
GCC 4.7 displays:
copy constructor
copy constructor
GCC 4.8 uses the move constructor in both cases.