Hi Alex, you say I "claim[s] that objects accessed via reference to const can modify themselves". Which paragraph are you referring to? It don't see that? I'm happy to update the article to make it clearer. Thanks, Craig
That is accurate. The object reference by immutable msg is hello_world which is mutable. If the value of hello_world changes, then the value of msg changes too. In the simple example, it won't happen, but the point is that it could, especially when multi-threaded.
I don't buy it, by calling that function, the caller is saying the value won't change underneath you (because you are being called in multithreaded code). The object itself cannot change itself, though of course there can be mutable members, which tend to be syncronisation mechanisms (mutexes) anyway.
No, the caller is saying that the callee isn't permitted to change the value! That's what const means. No promise is made about the actual object itself. There's no general way to specify that in C++.
This isn't so much an issue for functions, where the caller is stopped while the callee runs, but it needs to be borne in mind for longer-lived objects that take const references - or, indeed, const pointers - to objects that have longer lifetimes again. (I don't think multithreading need be introduced for this to be an issue.)
There's quite a difference between an object that could change and one that won't, if you're considering caching values across method calls, or setting up the referring object based on the referred object's current state, etc.
Ok, I know what the spec says, but it is utterly rediculous to expect people to write code in which parameters are allowed to change in an undefined way.
> I don't buy it, by calling that function, the caller is saying the value won't change underneath you
You really do not understand what "const" means in C++.
If what you said were true, a whole class of optimizations would be possible that are not currently possible. For example, consider this silly function:
void f(const int *x) {
int y = *x;
g();
if (y == *x) h();
}
If what you said were true, this would be able to optimize away the call to h() completely, but if you compile the function with maximum optimization you will see that this is not the case. Why not? Because the caller could look like this:
int val = 0;
void g() { val = 1; }
int main() { f(&val); }
I'm toying with implementing O(1) copies by using a reference counted pointer to basic_string instead of storing it by value within the class. It would mean a small memory overhead and another level of indirection, but probably worth doing. Thoughts?
libc++ (the Clang project standard C++ library) doesn't use COW for std::string, and I'm pretty sure GNU stdlibc++ will also drop it the next time they have to break ABI. C++11 move semantics and the 'small string optimisation', as it's known, blow away any performance benefit of COW for most sane uses.
I don't have a copy of the final 2011 standard, but as far as I can see there's nothing in the latest 2011 draft of C++11 that expressly prohibits COW.
[21.4.1.4] does say "Every object of type basic_string<charT, traits, Allocator> shall use an object of type Allocator to allocate and free storage for the contained charT objects as needed", but that "as needed" most definitely leaves the door open for a COW implementation.
The only other part that I can see that may preclude a COW implementation is the postconditions specified for copy operations [21.4.2], which says data() returns a pointer which "points at the first element of an allocated copy of the array whose first element is pointed at by str.data()". Again though, "allocated copy" doesn't necessarily mean "a copy I just allocated". When I go and get a copy of a book I don't literally go and copy it.
In fact the standard specifies that the move constructor leave the source value "in a valid state with an unspecified value"... which again suggests you could use COW and have the source argument return the same value it had before you moved from it.
In the latest C++14 draft though (N3690), you're right, it is explicitly prohibited because "Invalidation is subtly different with reference-counted strings".
21.4.1 p6 states invalidation of iterators/references is only allowed for
— as an argument to any standard library function taking a reference to non-const basic_string as an argument.
— Calling non-const member functions, except operator[], at, front, back, begin, rbegin, end, and rend.
The non-const operator[] in a COW string requires making a copy of the string if the ref count > 1 which invalidates references and violates this paragraph.
Hmm true. This is what happens when you let the user violate the iterator abstraction for performance and rely on contiguity and raw refs/ptrs. It's kind of a shame, because the moment you mutating characters in a string you're often doing something silly anyway.
The class is movable already. Reference counting will allow a copy constructor or copy assignment to avoid copying the string object, just increase the reference count.