Imagine that we got an object passed to a function and would like move semantics to be applied to its individual members. Which option to choose: call std::move on each member or move the whole object (several times) and then access its members as rvalues?
Foo::Foo(Object object)
// this way?
: member1(std::move(object.member1)), member2(std::move(object.member2)) {}
// or this way?
: member2(std::move(object).member2), member2(std::move(object).member2) {}
To be more specific, consider the following example. There is a downloader class which accepts connection parameters as an object. I want to move-assign its members to the members of the downloader, so I'm doing the following:
class ConnectionParameters
{
public:
ConnectionParameters(string server, string login, string password, unsigned short port, unsigned int connectionLimit, unsigned long long speedLimit)
: server(move(server))
, login(move(login))
, password(move(password))
, port(port)
, connectionLimit(connectionLimit)
, speedLimit(speedLimit) {}
ConnectionParameters(const ConnectionParameters &) = delete; // disable copy constructor
ConnectionParameters(ConnectionParameters &&) = default; // default move constructor
const string & getServer() const & { return this->server; }
string && getServer() && { return move(this->server); }
const string & getLogin() const & { return this->login; }
string && getLogin() && { return move(this->login); }
const string & getPassword() const & { return this->password; }
string && getPassword() && { return move(this->password); }
unsigned short getPort() const { return this->port; }
unsigned int getConnectionLimit() const { return this->connectionLimit; }
unsigned long long getSpeedLimit() const { return this->speedLimit; }
private:
string server, login, password;
unsigned short port;
unsigned int connectionLimit;
unsigned long long speedLimit;
};
class Downloader
{
public:
Downloader(ConnectionParameters connectionParameters, string downloadDirectory, unsigned long long diskSpaceMinimum)
: server(move(connectionParameters).getServer()) // 1st move
, login(move(connectionParameters).getLogin()) // 2nd move!
, password(move(connectionParameters).getPassword()) // 3rd move!
, port(connectionParameters.getPort())
, connectionLimit(connectionParameters.getConnectionLimit())
, speedLimit(connectionParameters.getSpeedLimit())
, downloadDirectory(move(downloadDirectory))
, diskSpaceMinimum(diskSpaceMinimum) {}
private:
string server, login, password;
unsigned short port;
unsigned int connectionLimit;
unsigned long long speedLimit;
string downloadDirectory;
unsigned long long diskSpaceMinimum;
};
What I don't like in this approach is that std::move is called several times on the same connectionParameters object. Although it works, it probably violates the principle of move semantics (that the object can't be used after being moved).
An alternative approach would be to move members individually:
struct ConnectionParameters
{
string server, login, password;
unsigned short port;
unsigned int connectionLimit;
unsigned long long speedLimit;
ConnectionParameters(string server, string login, string password, unsigned short port, unsigned int connectionLimit, unsigned long long speedLimit)
: server(move(server))
, login(move(login))
, password(move(password))
, port(port)
, connectionLimit(connectionLimit)
, speedLimit(speedLimit) {}
ConnectionParameters(const ConnectionParameters &) = delete; // disable copy constructor
ConnectionParameters(ConnectionParameters &&) = default; // default move constructor
};
class Downloader
{
public:
Downloader(ConnectionParameters connectionParameters, string downloadDirectory, unsigned long long diskSpaceMinimum)
: server(move(connectionParameters.server))
, login(move(connectionParameters.login))
, password(move(connectionParameters.password))
, port(connectionParameters.port)
, connectionLimit(connectionParameters.connectionLimit)
, speedLimit(connectionParameters.speedLimit)
, downloadDirectory(move(downloadDirectory))
, diskSpaceMinimum(diskSpaceMinimum) {}
private:
string server, login, password;
unsigned short port;
unsigned int connectionLimit;
unsigned long long speedLimit;
string downloadDirectory;
unsigned long long diskSpaceMinimum;
};
What I don't like here is that members of the ConnectionParameters structure are directly exposed (otherwise, Downloader would not be able to move them).
What is the right way to do this?
Related
I run some tests to validate my preconceived idea about constructors speed but the results were very different from what I expected. Am I doing something wrong? What is that I'm missing?
I'm using MVS 2015 with no optimizations (but it doesn't matter the results are always equivalent).
class Person
{ using ushort = unsigned short;
string name_;
ushort age_;
public:
explicit Person(const string &name, ushort age) //1
{ name_ = name;
age_ = age;
}
explicit Person(const string &name,ushort age) : name_{name},age_{age} //2
{
}
explicit Person(string name, ushort age) : name_{ std::move(name) }, age_{ age } //3
{
}
};
//1 - Is always faster than the other ctors.
//2 - Is slightly slower than 1 (negligible!!).???
//3 - Takes 50% more time than 1. ???
I run the tests using small strings(8 bytes), 1K, 2K string sizes..
I was expecting //2 ctor to be the fastest, and not expecting that //3 took so much time.
A last question is it legal what I'm doing in //3 ctor?
UPDATE
Here's the code I wrote
class Person
{
using ushort = unsigned short;
private:
std::string name_;
ushort age_;
public:
static constexpr size_t nr_ctors = 3;
static const char * const ctor_signature[nr_ctors];
enum class CTOR_1
{ CTOR = 0
};
enum class CTOR_2
{ CTOR = 1
};
enum class CTOR_3
{ CTOR = 2
};
explicit Person(const std::string &name, ushort age,CTOR_1)
{ name_ = name;
age_ = age;
}
explicit Person(const std::string &name, ushort age, CTOR_2) : name_{name},age_{age}
{}
explicit Person(std::string name,ushort age,CTOR_3) : name_{std::move(name)},age_{age}
{}
};
const char * const Person::ctor_signature[Person::nr_ctors] = {"\nexplicit Person(const std::string &name, ushort age,CTOR_1)",
"\nexplicit Person(const std::string &name, ushort age, CTOR_2) : name_{name},age_{age}",
"\nexplicit Person(std::string name,ushort age,CTOR_3) : name_{std::move(name)},age_{age}"};
using mclock = std::chrono::high_resolution_clock;
using time_p_t = std::chrono::time_point<mclock>;
using precision_t = std::chrono::nanoseconds;
#define NR_ITERATIONS (128 * 1024)
template <typename Ty_>
precision_t time_no_heap(const std::string &name)
{
time_p_t t_0;
time_p_t t_1;
t_0 = mclock::now();
Person p = Person{name,66,Ty_::CTOR};
t_1 = mclock::now();
return t_1 - t_0;
}
template <typename Ty_>
precision_t time_with_heap(const std::string &name)
{
time_p_t t_0;
time_p_t t_1;
Person *p_person;
t_0 = mclock::now();
p_person = new Person{ name,66,Ty_::CTOR };
t_1 = mclock::now();
delete p_person;
return t_1 - t_0;
}
void print_statistics(int iterations, size_t str_size, const precision_t(&stats)[2][Person::nr_ctors])
{
std::cout << "\nTotal iterations : "
<< iterations
<< "\nString ize : "
<< str_size
<< std::endl;
for (int i = 0; i < Person::nr_ctors; ++i)
{ std::cout << Person::ctor_signature[i]
<< "\n\t Stack (ms) : "
<< std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stats[0][i]).count()
<< "\n\t Heap (ms) : "
<< std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stats[1][i]).count()
<< std::endl;
}
}
int main(int argc, const char *argv[])
{
int iterations;
std::string *p_name;
if (argc != 3 && argc != 1)
{ std::cout << "USAGE [<iterations>K <string size>]" << std::endl;
return -1;
}
else if (argc == 3)
{ iterations = std::atoi(argv[1]) * 1024;
p_name = new std::string(std::atoi(argv[2]), 'x');
}
else
{ iterations = NR_ITERATIONS;
p_name = new std::string{ "Benchmark" };
}
precision_t benchmark [2][Person::nr_ctors]{};
std::cout << "\nUsing string : " << *p_name << ".\nIterating : " << iterations << " times." << std::endl;
for (auto i = iterations; --i >= 0; )
{ //Stack allocation
benchmark[0][(int)Person::CTOR_1::CTOR] += time_no_heap<Person::CTOR_1>(*p_name);
benchmark[0][(int)Person::CTOR_2::CTOR] += time_no_heap<Person::CTOR_2>(*p_name);
benchmark[0][(int)Person::CTOR_3::CTOR] += time_no_heap<Person::CTOR_3>(*p_name);
//Heap allocation
benchmark[1][(int)Person::CTOR_1::CTOR] += time_with_heap<Person::CTOR_1>(*p_name);
benchmark[1][(int)Person::CTOR_2::CTOR] += time_with_heap<Person::CTOR_2>(*p_name);
benchmark[1][(int)Person::CTOR_3::CTOR] += time_with_heap<Person::CTOR_3>(*p_name);
}
print_statistics(iterations,p_name->size(),benchmark);
delete p_name;
return 0;
}
I was being mistaken by the debug version of MVS which presents results completely different and opposite to release version even with the same project settings! My mistake..
Benchmarking with no optimizations is meaningless.
What string are you passing as name? A very important factor is the length of the string, and whether or not it will trigger SBO (small buffer optimization).
If the string is short enough, moves won't be any faster than copies.
Also, what does your benchmark look like? It might be that your benchmarking code is flawed.
I would also expect //2 to be the fastest, lets have a look at what the difference constructors do.
//1: name_ is default constructed and then copy assigned.
//2: name_ is copy constructed.
//3: name is copy constructed and then name_ is move constructed.
The difference between //1 and //2 will grow as default construction becomes more expensive.
//3 is valid since the argument name only lives inside the constructor so we can steal the innards by move constructing name_.
Nicolai Josuttis have a great talk from CppCon 2017 on this.
I got some different results when I tried to benchmark your code.
The benchmark runs on a pool of AWS Machines, compiled with Clang 5.0 C++17 -O3.
#include <string>
class Person1
{ using ushort = unsigned short;
std::string name_;
ushort age_;
public:
explicit Person1(const std::string &name, ushort age) //1
{ name_ = name;
age_ = age;
}
};
class Person2
{ using ushort = unsigned short;
std::string name_;
ushort age_;
public:
explicit Person2(const std::string &name,ushort age) : name_{name},age_{age} //2
{
}
};
class Person3
{ using ushort = unsigned short;
std::string name_;
ushort age_;
public:
explicit Person3(std::string name, ushort age) : name_{ std::move(name) }, age_{ age } //3
{
}
};
static void CreatePerson1(benchmark::State& state)
{
for (auto _ : state) {
Person1 person("Hello World!!!!!!!!!!!!", 10);
}
}
BENCHMARK(CreatePerson1);
static void CreatePerson2(benchmark::State& state)
{
for (auto _ : state) {
Person2 person("Hello World!!!!!!!!!!!!", 10);
}
}
BENCHMARK(CreatePerson2);
static void CreatePerson3(benchmark::State& state)
{
for (auto _ : state) {
Person3 person("Hello World!!!!!!!!!!!!", 10);
}
}
BENCHMARK(CreatePerson3);
With a small string (8 bytes) the second constructor was the fastest followed by the third. See result here Quick C++ Benchmarks
With a larger string (20 bytes) the third constructor was the fastest followed by the second. See result here Quick C++ Benchmarks
You make a copy in the third case. Instead this, try something like this:
class Person
{ using ushort = unsigned short;
string name_;
ushort age_;
public:
explicit Person(string&& name, ushort age) : name_{ std::move(name) }, age_{ age } //3
{
}
explicit Person(const string &name, ushort age) //1
{
name_ = name;
age_ = age;
}
explicit Person(const string &name,ushort age) : name_{name},age_{age} //2
{
}
};
R-value usage has to improve the performance.
Or you can create one constructor for aboce r-value and l-value like this:
class Person
{ using ushort = unsigned short;
string name_;
ushort age_;
public:
template<typename T>
explicit Person(T&& name, ushort age) : name_{ std::forward<T>(name) }, age_{ age } //3
{
}
};
#include <iostream>
#include <set>
using namespace std;
class StudentT {
public:
int id;
string name;
public:
StudentT(int _id, string _name) : id(_id), name(_name) {
}
int getId() {
return id;
}
string getName() {
return name;
}
};
inline bool operator< (StudentT s1, StudentT s2) {
return s1.getId() < s2.getId();
}
int main() {
set<StudentT> st;
StudentT s1(0, "Tom");
StudentT s2(1, "Tim");
st.insert(s1);
st.insert(s2);
set<StudentT> :: iterator itr;
for (itr = st.begin(); itr != st.end(); itr++) {
cout << itr->getId() << " " << itr->getName() << endl;
}
return 0;
}
In line:
cout << itr->getId() << " " << itr->getName() << endl;
It give an error that:
../main.cpp:35: error: passing 'const StudentT' as 'this' argument of 'int StudentT::getId()' discards qualifiers
../main.cpp:35: error: passing 'const StudentT' as 'this' argument of 'std::string StudentT::getName()' discards qualifiers
What's wrong with this code? Thank you!
The objects in the std::set are stored as const StudentT. So when you try to call getId() with the const object the compiler detects a problem, mainly you're calling a non-const member function on const object which is not allowed because non-const member functions make NO PROMISE not to modify the object; so the compiler is going to make a safe assumption that getId() might attempt to modify the object but at the same time, it also notices that the object is const; so any attempt to modify the const object should be an error. Hence compiler generates an error message.
The solution is simple: make the functions const as:
int getId() const {
return id;
}
string getName() const {
return name;
}
This is necessary because now you can call getId() and getName() on const objects as:
void f(const StudentT & s)
{
cout << s.getId(); //now okay, but error with your versions
cout << s.getName(); //now okay, but error with your versions
}
As a sidenote, you should implement operator< as :
inline bool operator< (const StudentT & s1, const StudentT & s2)
{
return s1.getId() < s2.getId();
}
Note parameters are now const reference.
Member functions that do not modify the class instance should be declared as const:
int getId() const {
return id;
}
string getName() const {
return name;
}
Anytime you see "discards qualifiers", it's talking about const or volatile.
Actually the C++ standard (i.e. C++ 0x draft) says (tnx to #Xeo & #Ben Voigt for pointing that out to me):
23.2.4 Associative containers
5 For set and multiset the value type
is the same as the key type. For map
and multimap it is equal to pair. Keys in an associative
container are immutable.
6 iterator of
an associative container is of the
bidirectional iterator category. For
associative containers where the value
type is the same as the key type, both
iterator and const_iterator are
constant iterators. It is unspecified
whether or not iterator and
const_iterator are the same type.
So VC++ 2008 Dinkumware implementation is faulty.
Old answer:
You got that error because in certain implementations of the std lib the set::iterator is the same as set::const_iterator.
For example libstdc++ (shipped with g++) has it (see here for the entire source code):
typedef typename _Rep_type::const_iterator iterator;
typedef typename _Rep_type::const_iterator const_iterator;
And in SGI's docs it states:
iterator Container Iterator used to iterate through a set.
const_iterator Container Const iterator used to iterate through a set. (Iterator and const_iterator are the same type.)
On the other hand VC++ 2008 Express compiles your code without complaining that you're calling non const methods on set::iterators.
Let's me give a more detail example. As to the below struct:
struct Count{
uint32_t c;
Count(uint32_t i=0):c(i){}
uint32_t getCount(){
return c;
}
uint32_t add(const Count& count){
uint32_t total = c + count.getCount();
return total;
}
};
As you see the above, the IDE(CLion), will give tips Non-const function 'getCount' is called on the const object. In the method add count is declared as const object, but the method getCount is not const method, so count.getCount() may change the members in count.
Compile error as below(core message in my compiler):
error: passing 'const xy_stl::Count' as 'this' argument discards qualifiers [-fpermissive]
To solve the above problem, you can:
change the method uint32_t getCount(){...} to uint32_t getCount() const {...}. So count.getCount() won't change the members in count.
or
change uint32_t add(const Count& count){...} to uint32_t add(Count& count){...}. So count don't care about changing members in it.
As to your problem, objects in the std::set are stored as const StudentT, but the method getId and getName are not const, so you give the above error.
You can also see this question Meaning of 'const' last in a function declaration of a class? for more detail.
I have a structure that should be statically initialized.
struct Option
{ char Option[8];
void (*Handler)(const char* value);
};
void ParseInto(const char* value, const char** target); // string option
void ParseInto(const char* value, int* target, int min, int max); // int option
static int count = 1;
static const char* name;
static const Option OptionMap[] =
{ { "count", [](const char* value) { ParseInto(value, &count, 1, 100); } }
, { "name", [](const char* value) { ParseInto(value, &name); } }
// ...
};
Up to this point it works.
To get rid of repeating the lambda function definition over and over (there are dozens) I want to use a factory like this:
struct Option
{ const char Option[8];
const void (*Handler)(const char* value);
template<typename ...A>
Option(const char (&option)[8], A... args)
: Option(option)
, Handler([args...](const char* value) { ParseInto(value, args...); })
{}
};
static const Option OptionMap[] =
{ { "count", &count, 1, 100 }
, { "name", &name }
};
This does not work for two reasons:
I did not find a type for the first constructor parameter option that perfectly forwards the initialization of the character array. The difficult part is that the length of the assigned array does not match the array length in general.
The even harder part is that the lambda function has a closure and therefore cannot decay to a function pointer. But all parameters are compile time constants. So It should be possible to make the constructor constexpr. However, lambdas seem not to support constexpr at all.
Anyone an idea how to solve this challenge?
The current work around is a variadic macro. Not that pretty, but of course, it works.
Context is C++11. I would not like to upgrade for now, but nevertheless a solution with a newer standard would be appreciated. Problems like this tend to reappear from time to time.
There are some further restrictions by the underlying (old) code. struct Option must be a POD type and the first member must be the character array so a cast from Option* to const char* is valid.
I am trying to attach bits of data, called components, to an Entity node.
However, whenever I attach a derived component type to the Entity node, it is being upcasted to the base Component type instead of staying a child. How could I prevent this so I can make use of std::type_index?
Currently, because the Component is being upcasted, std::type_index keeps making an index for Component, rather than one of the child classes. Do I have to template out the AttachComponent method? I'd rather not.
class Entity final
{
//Typedefs
typedef std::map<std::type_index, std::shared_ptr<acorn::Component> > ComponentMap;
ComponentMap m_components;
unsigned long int m_ID;
private:
public:
explicit Entity(unsigned long int id);
~Entity();
//Getters
inline unsigned long int GetID() const
{
return m_ID;
}
template<class ComponentType>
std::weak_ptr<ComponentType> GetComponent()
{
auto component_map_it = m_components.find(std::type_index(typeid(ComponentType)));
//If the component was found
if (component_map_it != m_components.end()) {
//Get the shared_ptr of the component
std::shared_ptr<acorn::Component> base_ptr = component_map_it->second;
//Cast it to the desired component type
std::shared_ptr<ComponentType> converted_ptr(std::static_pointer_cast<ComponentType>(base_ptr));
//Convert to a weak pointer
std::weak_ptr<ComponentType> return_ptr(converted_ptr);
//Return the weak pointer
return return_ptr;
}
else {
//If no component type was found, return a nullptr.
return std::weak_ptr<ComponentType>();
}
}
//Setters
inline void AttachComponent(std::shared_ptr<Component> component)
{
auto raw_ptr = component.get();
auto insert_pair = std::make_pair(std::type_index(typeid(raw_ptr)), component);
m_components.insert(insert_pair);
}
};`
Yes, you have to make AttachComponent a template. That's the only way to preserve the type information:
template <class T>
void AttachComponent(std::shared_ptr<T> component)
{
// as before
}
I have a class Foo that contains a multimap with an unsigned long and a reference to a Bar class.
I'm getting the error "no matching function to call multimap, etc".
What is wrong on my insert into the multimap?
[header file]
class Foo
{
public:
Foo();
~Foo();
void set(const unsigned long var1, const Bar& var2);
private:
struct map_sort
{
bool operator()(const unsigned long& e1, const unsigned long& e2) const
{
return (e2 < e1);
}
};
std::multimap<unsigned long,Bar&, map_sort> m_map;
};
[source file]
void Foo::set(const unsigned long var1, const Bar& var2)
{
m_map.insert(var1,rtime); //<-- ERROR!!!!!!!!
}
You multimap member is declared to take unsigned long and Bar&
std::multimap<unsigned long,Bar&, map_sort> m_map;
while, in source file
void Foo::set(const unsigned long var1, const Bar& var2)
{
m_map.insert(var1,rtime); //var1 is const here! (rtime is not declared but I assume you meant var2 - which is const, too)
}
you are trying to insert const unsigned long and const Bar& which differ from the ones declared above (they are constant). Note that the const unsigned long isn't really an issue because it is copied by value so, in fact, the multimap keeps its own version of the variable and the "main" one can't be modified whatsoever.
Additionally, check out the documentation of multimap's insert method http://www.cplusplus.com/reference/map/multimap/insert/. You should use std::pair to insert key-value pair:)
Solution 1: Change your method to take non-const arguments:
void set(unsigned long var1, Bar& var2);
Solution 2: Change your map to contain const values:
std::multimap<const unsigned long, const Bar&, map_sort> m_map
Working example:
class Foo
{
public:
Foo();
~Foo();
void set(const unsigned long var1, const Bar& var2);
private:
struct map_sort
{
bool operator()(const unsigned long& e1, const unsigned long& e2) const
{
return (e2 < e1);
}
};
std::multimap<const unsigned long, const Bar&, map_sort> m_map;
};
void Foo::set(const unsigned long var1, const Bar& var2)
{
m_map.insert(pair<const unsigned long, const Bar&>(var1, var2));
}