Allegro.cc - Online Community

Allegro.cc Forums » Off-Topic Ordeals » [rant] C++0x is ...so simple.

This thread is locked; no one can reply to it. rss feed Print
 1   2   3   4 
[rant] C++0x is ...so simple.
bamccaig
Member #7,536
July 2006
avatar

I'm currently attempting to build the current mainline of GCC (from the SVN repository) in Linux so I can experiment with some of the features... :D

** EDIT **

The tests (make [options] -k check) did not go well. :o Bad Things(TM) apparently happened... :-X syslogd wrote apocalyptic messages to my terminal indicating that a reboot was necessary... :-/ Seemed to be CPU temperature related... Anyway, I let make finish :P and then rebooted... I installed it into my home directory... :-/ Testing it out now...

Karadoc ~~
Member #2,749
September 2002
avatar

Well, I didn't read the whole article... but I can tell you this: C is better than C++. :p

Quote:

string s0("my mother told me that");
string s1("cute");
string s2("fluffy");
string s3("kittens");
string s4("are an essential part of a healthy diet");

And that you concatenate them like this:

string dest = s0 + " " + s1 + " " + s2 + " " + s3 + " " + s4;

...
Each call to operator+() returns a temporary string. There are 8 calls to operator+() , so there are 8 temporary strings. Each one, upon its construction, performs a dynamic memory allocation and copies all of the characters that have been concatenated so far, and later, upon its destruction, performs a dynamic memory deallocation.

'snprintf' in C doesn't have that problem (it has other problems instead). Remind me why are people extending an already bloated language? Do we really need even more ways to confuse people with our different coding styles in the same language?

</end flamebait>

-----------

axilmar
Member #1,204
April 2001

anonymous said:

Example, please. How do I take a pointer to a temporary and manage to pass it to a function (as a rvalue reference) at the same time? In addition, would you really take the address of a temporary?

For example:

#SelectExpand
1//accept rvalue reference 2void foo(const vector<int> &&v) { 3 //move semantics; v is emptied 4 vector<int> b(v); 5 6 //error 7 v[0]; 8} 9 10//return a temporary 11vector<int> bar() { 12 return vector<int>{1, 2, 3, 4}; 13} 14 15int main() { 16 //pass a temporary to a function 17 foo(bar()); 18}

The above is no different than using auto_ptrs really. Same problem.

Quote:

I don't entirely understand. Do you suggest that implementing copy-on-write (?) for classes is simpler? Could you show an example of how you'd implement a swap for a vector (and do rvalue references have only to do with swapping)?

class vector {
    char *data;
};

void swap(vector &a, vector &b) {
    swap(a.data, b.data);
}

Quote:

I have a feeling that a non-optional GC would just turn C++ into a completely different language (why not program in a GC language in the first place?) and optionally not using it would be out of the question if the standard way of doing things would be what you describe.

Indeed, but that's only because C++ contains lots of stuff to handle the memory management problem.

anonymous
Member #8025
November 2006

The example with vector doesn't work as you think - that program doesn't do anything bad (at least with GCC 4.4.1). I rewrote it with my non-copyable-but-moveable class which shows that it takes an explicit move:

#SelectExpand
1#include <iostream> 2 3class X 4{ 5 int* p; 6public: 7 X(): p(0) { std::cout << "X()\n"; } 8 X(int n): p(new int(n)) { std::cout << "X(int)\n"; } 9 X(const X&) = delete; 10 X(X&& x): p(x.p) { x.p = 0; std::cout << "X(X&& x)\n"; } 11 X& operator=(const X&) = delete; 12 X& operator= (X&& other) 13 { 14 if (this != &other) { 15 delete p; 16 p = other.p; 17 other.p = 0; 18 } 19 std::cout << "operator=(X&&)\n"; 20 return *this; 21 } 22 ~X() {delete p; std::cout << "~X()\n"; } 23 void print() const 24 { 25 if (p) { 26 std::cout << *p << '\n'; 27 } else { 28 std::cout << "NULL\n"; 29 } 30 } 31}; 32 33//using a const X&&, it has no option but to use X(const X&) even with std::move 34void foo(X &&x) 35{ 36 //X y(x); //this doesn't compile: tries to use X(const X&) 37 X y(std::move(x)); //this moves from x using X(X&&) 38 x.print(); 39 y.print(); 40} 41 42//return a temporary 43X bar() 44{ 45 return X(10); 46} 47 48int main() 49{ 50 //pass a temporary to a function 51 foo(bar()); 52}

The standard's committee is very well aware of the issues of auto_ptr. Do you really think they would deprecate std::auto_ptr for these reasons only to build the exact same behaviour into more user-defined objects? (Or GCC's implementation of rvalue references is completely broken.)

I think my general guideline works here: don't std::move if you can't put the object back into a good state (assuming you need to use it later on) or, in case you don't care about the value any longer, unless you have a non-moving overload that takes const X& (if you don't have that it doesn't matter in this example, if the X is a temporary or not).

void swap(vector &a, vector &b) {
    swap(a.data, b.data);
}

This is basically how it is with vector and other standard containers today. But this only helps if you call swap1. I guess I should have asked for the implementation of a full std::vector (e.g with copy constructor, operator=, push_back, operator[]).

-----

1 With GCC 3.5 I discovered (wondering why my swap overload for a user-defined class is not called by std::sort) that the standard library uses the std::iter_swap algorithm for swapping values held in iterators, which was essentially:

template <class I1, class I2> iter_swap(I1 it1, I2 it2) {
    X temp = *it1;
    *it1 = *it2;
    *it2 = temp;
}

I just replaced that with a forwarded call to swap (which made for example sorting strings quite a bit faster, as std::string has a specialized swap):

swap(*it1, *it2);

However, looking at the implementation of GCC 4.4, it seems that my "fix" may not have been entirely correct. If I'm not mistaken that implementation forwards to swap only if the value_types of both iterators are identical, otherwise doing something like:

X x(move(*it1));
*it1 = move(*it2);
*it2 = move(x);

I suppose that if the value_types are not the same (but convertible to each other), one might get annoying error messages complaining that the algorithm cannot choose the swap instantiation (since both types have to be the same). I didn't run into such situations, though, but I suppose this could happen with algorithms that work with 2 different ranges.

'snprintf' in C doesn't have that problem (it has other problems instead). Remind me why are people extending an already bloated language? Do we really need even more ways to confuse people with our different coding styles in the same language?

</end flamebait>

There should be quite a number of ways to get better performance:

#SelectExpand
1//1 operator+= 2string dest; 3dest.reserve(lenght_of_concatenated_string); 4dest += a; 5dest += " "; 6dest += b; 7... 8 9//2 stringstream (but doesn't stringstream still need to reallocate the buffer?) 10stringstream ss; 11ss << a << " " << b << ...; 12string dest = ss.str(); 13 14//3 expression templates (unless implemented by STL writers might look something like: 15//tons of complex templates here 16 17string dest = concatenator() + a + " " + b + " " + ...;

The C++ alternatives exist because alternative C ways tend to be rather error-prone. But you can fall back to using them if they indeed provide the performance you absolutely require.

If the only rationale for rvalue references was performance of string concatenation, then that would be quite lame indeed.

axilmar
Member #1,204
April 2001

anonymous said:

The example with vector doesn't work as you think - that program doesn't do anything bad (at least with GCC 4.4.1).

I think 'const' should be removed from the parameter 'v' of function 'foo'. A const rvalue reference should not allow for move semantics normally, because constness would be violated.

//accept rvalue reference
void foo(vector<int> &&v) {
    //move semantics; v is emptied
    vector<int> b(v);
    //error
    v[0];
}

Quote:

The standard's committee is very well aware of the issues of auto_ptr. Do you really think they would deprecate std::auto_ptr for these reasons only to build the exact same behaviour into more user-defined objects?

Well, perhaps, but then why do they give an example of 'clone_ptr' that works like std::auto_ptr?

Quote:

This is basically how it is with vector and other standard containers today. But this only helps if you call swap1. I guess I should have asked for the implementation of a full std::vector (e.g with copy constructor, operator=, push_back, operator[]).

It's very easy:

struct VectorData {
};

class vector {
    shared_ptr<VectorData> data;
};

You get copy construction, assignment, etc for free.

string s0("my mother told me that");
string s1("cute");
string s2("fluffy");
string s3("kittens");
string s4("are an essential part of a healthy diet");

And that you concatenate them like this:

string dest = s0 + " " + s1 + " " + s2 + " " + s3 + " " + s4;

...
Each call to operator+() returns a temporary string. There are 8 calls to operator+() , so there are 8 temporary strings. Each one, upon its construction, performs a dynamic memory allocation and copies all of the characters that have been concatenated so far, and later, upon its destruction, performs a dynamic memory deallocation.

I don't see how move semantics allows for any significant number of less allocations/deallocations. At best, the buffer of the temporary result is reallocated using 'realloc'. At worst, the temporary buffer would be extended by allocating a new larger buffer, copying the data and then freeing the old buffer. Still, nothing that could not be achieved with implicit sharing.

anonymous
Member #8025
November 2006

axilmar said:

I think 'const' should be removed from the parameter 'v' of function 'foo'. A const rvalue reference should not allow for move semantics normally, because constness would be violated.

No, even without the const it doesn't move anything (my example above clearly shows that it wants to use the X(const X&) constructor). I may be mistaken but it would make sense if the general guideline concerning move semantics would be: if it has a name, then you need to ask explicitly for moving; things are implicitly moved only if they really don't have a name. (One must read and reread the linked article because it explains in great detail what binds to what, but seems to be lacking such general guidelines...)

Quote:

Well, perhaps, but then why do they give an example of 'clone_ptr' [www.artima.com] that works like std::auto_ptr?

It doesn't. Again, if my interpretation is correct, it only takes the short-cut implicitly if we are talking about true temporaries, e.g:

clone_ptr<X> x = foo();

Quote:

It's very easy:

Ok, and if I want a copy (e.g what happens if I pass it by value). What happens if I do:

vector<X> v1;
vector<X> v2 = v1;
...
v2[0] = X(...); //does it affect v1?

Quote:

I don't see how move semantics allows for any significant number of less allocations/deallocations. At best, the buffer of the temporary result is reallocated using 'realloc'. At worst, the temporary buffer would be extended by allocating a new larger buffer, copying the data and then freeing the old buffer. Still, nothing that could not be achieved with implicit sharing.

Probably also depends on the allocation strategy? Still, I'd love to see an example with implicit sharing.

BTW, they say that implementators have given up trying to optimize std::string with COW, since it has turned out to be a pessimization (if you have COW in mind)? Some thread-safety issues?

axilmar
Member #1,204
April 2001

anonymous said:

No, even without the const it doesn't move anything (my example above clearly shows that it wants to use the X(const X&) constructor). I may be mistaken but it would make sense if the general guideline concerning move semantics would be: if it has a name, then you need to ask explicitly for moving; things are implicitly moved only if they really don't have a name. (One must read and reread the linked article because it explains in great detail what binds to what, but seems to be lacking such general guidelines...)

Ok. Even if you do

//accept rvalue reference
void foo(vector<int> &&v) {
    //move semantics; v is emptied
    vector<int> b(move(v));
    //error
    v[0];
}

there is still a problem. The whole operation that empties v might be buried into some other code.

Quote:

It doesn't. Again, if my interpretation is correct, it only takes the short-cut implicitly if we are talking about true temporaries, e.g:

clone_ptr<X> x = foo();

It does. Check the following piece of code:

clone_ptr p1(new derived);
// ...
clone_ptr p2 = std::move(p1);  // p2 now owns the pointer instead of p1
p1->foo(); //boom!!!

Exactly the same as:

auto_ptr<derived> p1(new derived);
auto_ptr<derived> p2 = p1;  // p2 now owns the pointer instead of p1
p1->foo(); //boom!!!

Quote:

Ok, and if I want a copy (e.g what happens if I pass it by value). What happens if I do:

vector<X> v1;
vector<X> v2 = v1;
...
v2[0] = X(...); //does it affect v1?

You clone the object by using a specific function.

Quote:

Probably also depends on the allocation strategy? Still, I'd love to see an example with implicit sharing.

Check out QString.

Quote:

BTW, they say that implementators have given up trying to optimize std::string with COW, since it has turned out to be a pessimization (if you have COW in mind)? Some thread-safety issues?

Example? link?

anonymous
Member #8025
November 2006

axilmar said:

there is still a problem. The whole operation that empties v might be buried into some other code.

I still don't see how that is any worse than any other bug buried in some other code.

As far as I understand there seems to be a rather simple guideline how to use move semantics safely. You need two overloads: one that picks up temporaries with && parameter which you can safely move since nobody will be missing them, and another that picks up other things with (const)& which you shouldn't move. (Or the function is like swap in that eventually everything is back to normal.) The clone_ptr has those overloads (constructor and assignment), my X class has those (except copy constructor/copy assignment are disabled), std::string::operator+ has those overloads etc.

Part of your misunderstanding seems to be that var is a rvalue in the following:

void foo(X&& var);

whereas it seems to me that this picks up temporaries (and other things if there are no more overloads), but var itself is lvalue which won't be implicitly affected by move semantics. rvalue references let you implement move semantics for your classes (a means to separate temporaries from named variables), but they don't by themselves move things.

Quote:

It does. Check the following piece of code

But the crucial difference is that with clone_ptr you explicitly ask it to be moved. It's not really that much different from the following (except this is a much nastier bug - an object which has been cleaned by moving at least is supposed to be in some consistent state, so it can at least be destroyed if not reused):

X* p = new X;
...
delete p;
...
*p;

Quote:

You clone the object by using a specific function.

Unfortunately, changing completely the semantics of existing libraries is out of the question.

Quote:

Example? link?

It is discussed here.

axilmar
Member #1,204
April 2001

anonymous said:

I still don't see how that is any worse than any other bug buried in some other code.

It is worse because the move might be implicit, i.e. programmer A inserts an optimization using move semantics, programmer B does not know that programmer A inserted an optimization, the application crashes.

Quote:

one that picks up temporaries with && parameter which you can safely move since nobody will be missing them

That is the problem: they might go missed.

Quote:

whereas it seems to me that this picks up temporaries (and other things if there are no more overloads), but var itself is lvalue which won't be implicitly affected by move semantics. rvalue references let you implement move semantics for your classes (a means to separate temporaries from named variables), but they don't by themselves move things.

Yes, I know, rvalue references do not move things by themselves, you have to implement the move operation. The problem is that moving data around is problematic: you don't know where a reference to a temporary might be stored.

Quote:

But the crucial difference is that with clone_ptr you explicitly ask it to be moved.

Don't be confused because you see std::move being called. I might have done the following:

clone_ptr p1(new derived);
// ...
clone_ptr p2 = haha(p1);  // p2 now owns the pointer instead of p1
p1->foo(); //boom!!!

and have the same results. No move insight, data of p1 are explicitly moved inside the function 'haha'.

It's not really that much different from the following

Exactly.

Unfortunately, changing completely the semantics of existing libraries is out of the question.

Why? progress requires sacrifices.

anonymous said:

BTW, they say that implementators have given up trying to optimize std::string with COW, since it has turned out to be a pessimization (if you have COW in mind)? Some thread-safety issues?

I read the material you posted. It's obvious: in all operations where COW is required, you have to lock the existing shared instance of data for as long as the operation takes place. For example, if you need to change a character in a string, you have to lock the instance, change the character, unlock the instance, in order to disallow modification from another thread. If another thread needs to modify the same string concurrently, the other thread needs to wait for the first thread to finish its operation before attempting to use the string.

The real problem lies with the implementation of strings though. In Java, strings are immutable for a reason, i.e. to avoid the overhead mentioned in the article you posted.

By the way, move semantics are dangerous when using threads as well.

anonymous
Member #8025
November 2006

axilmar said:

It is worse because the move might be implicit, i.e. programmer A inserts an optimization using move semantics, programmer B does not know that programmer A inserted an optimization, the application crashes.

I agree that you still need to know what you are doing. You can't simply move things because you just feel like it. (If one gets burnt by that a couple of times, perhaps one will stop moving things without being sure that it is valid.)

Quote:

That is the problem: they might go missed.

Yes, I know, rvalue references do not move things by themselves, you have to implement the move operation. The problem is that moving data around is problematic: you don't know where a reference to a temporary might be stored.

How can one miss temporaries. A temporary is something that there cannot be any more references to? (A temporary is not a local variable - it is the return value of a function or a literal or an unnamed object like x = X(...))

Or may-be you have something like this in mind:

#SelectExpand
1//X class from previous posts 2 3class Y 4{ 5 X x; 6public: 7 Y(): x(10) {} 8 X&& get() { return x; } 9}; 10 11int main() 12{ 13 Y y; 14 X x = y.get(); 15 x.print(); 16 y.get().print(); 17}

It seems, the general guideline derived from this would be: don't give out rvalue references to things that you don't want to be moved from you (similarly like you wouldn't give out a non-const reference to something that you didn't want modified). If get returned a X or a X&, it would call the X(const X&) constructor - which is disabled for X.

Quote:

No move insight, data of p1 are explicitly moved inside the function 'haha'.

Fire the programmer responsible for haha?

There can be bugs everywhere.

Quote:

Why? progress requires sacrifices.

I suppose one could provide an alternative library with different names (and then get lots of whining from people about why there are so many alternatives to do the same thing), but can you really imagine that the meaning of each and every line using standard libraries changed radically with a new implementation? A few days ago I replaced a Vector2D overloading operators with free functions operating on tuples in Python: it took a couple of hours, fixing dozens of errors and bugs which all basically resulted from operator+ changing its meaning in some 1000 lines of code.

Quote:

The real problem lies with the implementation of strings though. In Java, strings are immutable for a reason, i.e. to avoid the overhead mentioned in the article you posted.

I haven't got this immutable strings thing. So, if I want to convert a single character to uppercase in a string (not an uncommon thing to do), do I get a whole new string (somehow without any overhead) or is it still basically COW?

--

All in all, it seems that your general complaint about C++0x is that it doesn't magically turn C++ in a safe language where you don't need to know what you are doing. This seems a bit unreasonable expectation with a language that is C++. Instead what you get with C++0x is that certain things are easier to do if you know what you are doing.

axilmar
Member #1,204
April 2001

anonymous said:

A temporary is not a local variable

A temporary is allocated on the stack if does not fit a machine register - which makes it a local variable. This variable may be unnamed when returned, but it can be passed as an argument to a function and therefore get a name. Then it might be moved (by explicitly calling std::move) and therefore cause a problem.

Quote:

Fire the programmer responsible for haha?

It's cool to joke like this, unless it is you that is going to be fired. Add a loan to that, and a wife and kids, and you will see it's not as rosy as you think.

Quote:

I suppose one could provide an alternative library with different names (and then get lots of whining from people about why there are so many alternatives to do the same thing), but can you really imagine that the meaning of each and every line using standard libraries changed radically with a new implementation?

Or deprecate the existing STL (or parts of it) and use new code. Nobody uses std::string anyway.

Quote:

I haven't got this immutable strings thing. So, if I want to convert a single character to uppercase in a string (not an uncommon thing to do), do I get a whole new string (somehow without any overhead) or is it still basically COW?

If you want to process strings in Java, you use a StringBuffer object.

Quote:

All in all, it seems that your general complaint about C++0x is that it doesn't magically turn C++ in a safe language where you don't need to know what you are doing. This seems a bit unreasonable expectation with a language that is C++. Instead what you get with C++0x is that certain things are easier to do if you know what you are doing.

All in all, my general complaint about C++ is that safety comes second, performance first, where it should have been the opposite. Performance can be increased by throwing more money to the problem, safety can not. Safety costs billions of dollars each year to the IT industry.

Sevalecan
Member #4,686
June 2004
avatar

Whine, whine, whine.

As for me, nothing about the C++ language itself really bothers me, and I'd love to see it extended.

However, the standard library is a bit lacking. I mean.. What if I don't want to use the PITA C Berkeley sockets interface? Gotta go grab a wrapper library, all of them seem pretty crappy. They don't know how to balance a good api with functionality.

Boost has some really neat stuff, but it's just... really big.. Though I suppose it's ok if you're not running Gentoo, Lunar Linux, or Sourcemage and thus don't have to compile it yourself. God does it take a long time to compile, and it uses alot of memory too. But wait! Half the libraries are all crammed into the headers anyway! What am I always spending so much time compiling then?

Qt also has some interesting stuff, and Qt 4 is pretty modular. But pretty much everything outside of QtCore needs the event loop running to even work. Ah just what I needed, to write a bunch of impractical code just to take advantage of the QHttp class in a very small program.

Could I at least ask for an auto_ptr that deals with arrays?

In spite of all that, C++ is still my primary language. Granted, most languages I know outside of it are scripting languages (perl, PHP, bash). Maybe some day I should try learning Java or C#.. Not that I would ever let C# or anything else steal my love for C++. I know how everyone always balks at me about how great they think C# is! WELL YOU'LL NEVER TAKE ME ALIVE!

TeamTerradactyl: SevalecanDragon: I should shoot you for even CONSIDERING coding like that, but I was ROFLing too hard to stand up. I love it!
My blog about computer nonsense, etc.

bamccaig
Member #7,536
July 2006
avatar

axilmar said:

Don't be confused because you see std::move being called. I might have done the following...

Yes, you could have. But you should be fired and your code should be removed from the project for it. ;D Just like other low-level details, move semantics requires you to follow safe practices because just like deleting pointers that you don't own, they can be destructive if used wrongly. Just because you can turn an lvalue into an rvalue doesn't mean you necessarily should. I think move semantics are meant to be used sparingly in places where they shouldn't cause problems. If your method receives an rvalue then it should be expected that it actually refers to an rvalue (i.e., originally, it was a temporary). Further, you probably shouldn't implement move semantics in random functions or methods; at least not without being very explicit about it.

You pretty much need to go through std::move to turn the lvalue (which it becomes as soon as you pass it) back to an rvalue for it to have any affect under normal circumstances (obviously a function or method accepting T & shouldn't be passing it into std::move because as far as it knows it isn't an rvalue) so you will know that it's being moved.

anonymous
Member #8025
November 2006

axilmar said:

A temporary is allocated on the stack if does not fit a machine register - which makes it a local variable. This variable may be unnamed when returned, but it can be passed as an argument to a function and therefore get a name. Then it might be moved (by explicitly calling std::move) and therefore cause a problem.

You can actually explicitly move stuff if it passed by regular reference or by value (in the latter case the caller will naturally be uneffected). The question is, where will moving happen implicitly. (I would sort of trust the standard committee has considered all kinds of scenarios to be sure that it cannot hurt you "mysteriously". Or perhaps we can come up with a scenario that would make them take it back. "Explicitly moving things moves things even if I don't want to move them" doesn't seem enough to convince them.

Quote:

It's cool to joke like this, unless it is you that is going to be fired. Add a loan to that, and a wife and kids, and you will see it's not as rosy as you think.

I didn't mean to imply that people should be fired over a single bug they produce, but if this hypothetical person keeps repeating the same mistake over and over despite being told (how) to stop abusing rvalue references and std::move, wouldn't he be happier in a different line of work (or with a different language). I don't entirely understand why such low standards should be acceptable in the programming trade (employee cannot and doesn't want to learn new things, cannot follow moderately simple guidelines, doesn't really know what he's doing). How come someone has been a successful C++ programmer who doesn't screw up badly all along and then suddenly turns out incapable of "getting" rvalue references despite repeated failures in critical projects?

Quote:

Nobody uses std::string anyway.

Well, not in programs using unicode. (I've heard that string is no good for that but mostly because GUI libraries seem to come with their own string class that is used everywhere, so it would be more effort to use std::string anyway.)

But is there such a perfect replacement? (For example, the reference that you linked says this about operator[]: "If i is beyond the length of the string then the string is expanded with QChar::nulls, so that the QCharRef references a valid (null) character in the string." Well, it is sort of safe but is this really something that you'd actually want to happen?)

Quote:

If you want to process strings in Java, you use a StringBuffer object.

I still don't get how I'd start with s = "Hello" and end up with s = "hello" without any overhead in between compared to s[0] = tolower(s[0]) with mutable strings. (I could understand how this StringBuffer is good if you want to modify many characters in the string.)

Quote:

Performance can be increased by throwing more money to the problem, safety can not. Safety costs billions of dollars each year to the IT industry.

I couldn't tell not being a programmer (and I suppose I'm lucky :)). Is there any statistics comparing money thrown to it to increase performance vs safety costs? How much of these safety costs come directly from C++ being unsafe? (I suppose you can write buggy programs in any language, except perhaps bugs are simpler/faster to discover/fix in safer languages if there happens to be anyone competent around.)

bamccaig
Member #7,536
July 2006
avatar

axilmar said:

By the way, move semantics are dangerous when using threads as well.

I could be being shortsighted, but I don't see how move semantics are going to have any affect on threading. They should only be used on temporary values, which means nothing else in the universe even knows of its existence, including other threads.

anonymous said:

X&& get() { return x; }

I could be wrong, but I don't think that's valid. x is an lvalue. The only way to make it work is to wrap x in std::move or something similar to explicitly change it to an rvalue. Which would be wrong.

axilmar
Member #1,204
April 2001

bamccaig said:

Yes, you could have. But you should be fired and your code should be removed from the project for it. ;D Just like other low-level details, move semantics requires you to follow safe practices because just like deleting pointers that you don't own, they can be destructive if used wrongly. Just because you can turn an lvalue into an rvalue doesn't mean you necessarily should. I think move semantics are meant to be used sparingly in places where they shouldn't cause problems. If your method receives an rvalue then it should be expected that it actually refers to an rvalue (i.e., originally, it was a temporary). Further, you probably shouldn't implement move semantics in random functions or methods; at least not without being very explicit about it.

You pretty much need to go through std::move to turn the lvalue (which it becomes as soon as you pass it) back to an rvalue for it to have any affect under normal circumstances (obviously a function or method accepting T & shouldn't be passing it into std::move because as far as it knows it isn't an rvalue) so you will know that it's being moved.

Your argument pretty much boils down to "use move safely". But you ignore a lot of factors: complexity, large number of lines of code, multiple programmers leaving and entering a project, changes after months or years etc.

anonymous said:

"Explicitly moving things moves things even if I don't want to move them" doesn't seem enough to convince them.

You saw the problems that moving might create above. Need I repeat myself?

Quote:

but if this hypothetical person keeps repeating the same mistake over and over despite being told (how) to stop abusing rvalue references and std::move, wouldn't he be happier in a different line of work (or with a different language)

It might be a single mistake done once, discovered late and cost millions.

Quote:

How come someone has been a successful C++ programmer who doesn't screw up badly all along and then suddenly turns out incapable of "getting" rvalue references despite repeated failures in critical projects?

Over a long period of time, destructive updating is dangerous. It just does not scale well.

Quote:

But is there such a perfect replacement?

They could have thrown an exception.

Quote:

I still don't get how...

Just use a StringBuffer, which is mutable.

bamccaig said:

I could be being shortsighted, but I don't see how move semantics are going to have any affect on threading.

What if two threads decide to move things from the same variable?

bamccaig
Member #7,536
July 2006
avatar

axilmar said:

They should have thrown an exception.

Fixed. ;)

axilmar said:

What if two threads decide to move things from the same variable?

They won't because you only move rvalues, which are temporary and inaccessible to the rest of the universe. If a programmer wants to break best practice and start passing lvalues around as rvalues then he'd better either know what he's doing (and even then, probably shouldn't) or hate his feet. Used correctly, the same temporary variable shouldn't be accessible to multiple threads. There is the potential for bugs, but no worse than with pointers.

axilmar
Member #1,204
April 2001

bamccaig said:

They won't because you only move rvalues, which are temporary and inaccessible to the rest of the universe. If a programmer wants to break best practice and start passing lvalues around as rvalues then he'd better either know what he's doing (and even then, probably shouldn't) or hate his feet. Used correctly, the same temporary variable shouldn't be accessible to multiple threads. There is the potential for bugs, but no worse than with pointers.

"Used correctly" is not the same as "compiler prevents it". We always try to do things correctly, but our programs are full of bugs, because we are humans and make mistakes. These problems must be diagnosed by the compiler.

Goalie Ca
Member #2,579
July 2002
avatar

I went through the standard documentations myself this morning and will summarize it in my own way (as clear as I can).

Quote:

Even though named rvalue references can bind to an rvalue, they are treated as lvalues when used.

Basically everywhere inside the function f(A&& a) a is now an lvalue. Why..

f(A&& a)
{
 g(a);
 h(a);
}

Well, in this case if a was treated as an rvalue then g(A&& a) and h(A&& a) would be called. G could basically pilfer a and then h and anything else would fail hard.

Being C++ though, with its ingenious workarounds such as const_cast, also includes casting from l-values to r-values!

Quote:

The client of a movable object might decide that he wants to move from an object even though that object is an lvalue. There are many situations when such a need might arise. One common example is when you have a full dynamic array of objects and you want to add to it. You must allocate a larger array and move the objects from the old buffer to the new. The objects in the array are obviously not rvalues. And yet there is no reason to copy them to the new array if moving can be accomplished much faster.

template <class T>
void
swap(T& a, T& b)
{
    T tmp(static_cast<T&&>(a));
    a = static_cast<T&&>(b);
    b = static_cast<T&&>(tmp);
}

So now we can swap data structures without writing our own swap functions inside T. Was it worth it? Who knows :P

Well. std::move is nothing more than a cast.

Quote:

Earlier this paper proposed that:
Named rvalue references are treated as lvalues.
This thought will now be completed:
Unnamed rvalue references are treated as rvalues.

#SelectExpand
1 template <class T> 2 inline 3 T&& 4 move(T&& x) 5 { 6 return static_cast<T&&>(x); 7 } 8 9 template <class T> 10 void 11 swap(T& a, T& b) 12 { 13 T tmp(move(a)); 14 a = move(b); 15 b = move(tmp); 16 }

In order for perfect forwarding, preserving r-values in case of r-values, you need to use std::move just as shown in previous example.

-------------
Bah weep granah weep nini bong!

anonymous
Member #8025
November 2006

axilmar said:

You saw the problems that moving might create above. Need I repeat myself?

Actually I didn't. It definitely is a new way to shoot yourself in the foot, but as seems habitual in C++, it still takes a moderate effort.

Your examples (if they compile or behave as you expect) seem very similar to:

delete p;
//OMG, *p is no more but I want to keep using it

except it seems to me that this sort of bug has much more severe consequences (what happens is not deterministic).

Quote:

It might be a single mistake done once, discovered late and cost millions.

Are you saying that you can't already make such a mistake in C++03 (or any other language) if deadlines are tight and people come and go? At least this is not a bug from undefined behaviour: if code asks object to be moved and object is moved, this happens every time (hopefully leaving NULL pointers and like behind).

Besides there seems to be a really simple cure against not being affected by move semantics: don't implement move constructor/assignment. How hard can that be?

Quote:

Just use a StringBuffer, which is mutable.

I didn't realize StringBuffer was an independent fully functional mutable string type. (I though it had something to do with a buffer of a String.) So, when I mostly don't modify I string, I use immutable String, and otherwise StringBuffer wins hands down. (I somehow got an impression earlier that you don't see a need for a mutable string if you have an immutable string.)

axilmar said:

These problems must be diagnosed by the compiler.

How is the compiler supposed to realize that you didn't want to move sth when you explicitly told it to move it?

bamccaig
Member #7,536
July 2006
avatar

Just finished reading part 1 too. Lambda's also look useful and I suppose the auto keyword will also help to keep code clean. static_assert sounds quite useful as well. I don't know what you people are complaining about. :P

Goalie Ca
Member #2,579
July 2002
avatar

I'm not convinced lambda is all that great on its own in a non-functional language like C++. The real benefit comes with closures but even so its not powerful enough because functions are not first class objects in this language :(

#SelectExpand
1//Both examples end up looking like a for loop. 2 3//the pos function over a vector 4//syntactically not too bad but auto would make something quite similar 5std::foreach(v.begin(), v.end(), [&](double a) { 6 if (a<0) 7 a = 0; 8}); 9 10//aah. but now we get interesting. notice how i don't use maximum_element because i can keep it simple and use the closure to cache the magnitude calculation. 11double max_so_far = 0; 12std::foreach(v.begin(), v.end(), [&](Vector4 a){ 13 double b = a.magnitude(); 14 if (b>max_so_far) 15 max_so_far = b; 16});

I'm still experimenting with other uses. The idea of supplying my own lambda function for sort or priority_queue is much more appealing than writing another function or having to hack something stupid into the operator<

edit: i forgot to mention that the whole point of using foreach and library functions is because you can parallelize and optimize it. notice the complete lack of explicit loop variables :-)

-------------
Bah weep granah weep nini bong!

axilmar
Member #1,204
April 2001

anonymous said:

Are you saying that you can't already make such a mistake in C++03 (or any other language) if deadlines are tight and people come and go? At least this is not a bug from undefined behaviour: if code asks object to be moved and object is moved, this happens every time (hopefully leaving NULL pointers and like behind).

Of course you can. One more reason that C++1x is even more dangerous than existing C++.

Quote:

Besides there seems to be a really simple cure against not being affected by move semantics: don't implement move constructor/assignment. How hard can that be?

What about those in the STL? or other libraries? a vector move, for example, might be buried deep in some code.

Quote:

How is the compiler supposed to realize that you didn't want to move sth when you explicitly told it to move it?

There are ways to do it. Uniqueness typing, for example, allows for checking the sequence of mutations of a variable. Of course that's extremely advanced, on the forefront of programming language theory, not suitable for C++.

anonymous
Member #8025
November 2006

axilmar said:

What about those in the STL? or other libraries? a vector move, for example, might be buried deep in some code.

Libraries are hopefully properly tested. Why do you trust std::vector even now as it is (if you do)? (You could write your own, so at least you'll know all bugs are made by you :))

For example, a std::vector would use moving when it resizes. If your class doesn't provide move semantics, then instances will be copied over as before. It would also seem that implementing a non-throwing move constructor is a lot simpler than implementing a non-throwing copy constructor (but I'm not sure how one would achieve the same kind of exception safety for vector resizing as it currently has if the move construction should throw nevertheless [a quick test shows at least this implementation doesn't leave the contents as they were when an exception is thrown from move constructor]).

axilmar
Member #1,204
April 2001

anonymous said:

Libraries are hopefully properly tested. Why do you trust std::vector even now as it is (if you do)? (You could write your own, so at least you'll know all bugs are made by you :))

Or so you think.

EDIT:

C++0x has one big positive thing: lambdas. Extremely extremely extremely useful, and C++0x worths the trouble for lambdas alone.

Here is a simple piece of code I wrote in Visual Studio 2010:

#SelectExpand
1#include <iostream> 2#include <vector> 3#include <algorithm> 4using namespace std; 5 6void haha(vector<int> &v) { 7 vector<int> v1 = move(v); 8} 9 10int wmain() { 11 vector<int> v; 12 v.push_back(1); 13 v.push_back(2); 14 v.push_back(3); 15 for_each(v.begin(), v.end(), [](int i) {cout << i << endl;}); 16 haha(v); 17 for_each(v.begin(), v.end(), [](int i) {cout << i << endl;}); 18 getchar(); 19 return 0; 20}

Notice how easy looping has become.

By the way, I wrote the above in order to test moving. The second loop prints nothing, as expected, because the input is emptied above. Major sh1t is going to hit the fan with this...

 1   2   3   4 


Go to: