C++ default operator= implementation
Moderator: Coders of Rage
- TheBuzzSaw
- Chaos Rift Junior
- Posts: 310
- Joined: Wed Dec 02, 2009 3:55 pm
- Current Project: Paroxysm
- Favorite Gaming Platforms: PC
- Programming Language of Choice: C++
- Contact:
C++ default operator= implementation
The more I work in C++, the more I've noticed a theme in my object design. To avoid repeating code, I often make a copy function and a destroy function. My copy constructor calls the copy function. My destructor calls the destroy function. My operator overload calls destroy followed by copy. Would this not be a superior default implementation?
This has crossed my mind due to my recent interest in language design. It would be so much nicer being able to simply define the copy constructor, move constructor, and the destructor. The = operator would (if not defined otherwise) simply call an in-place destructor (not release the memory, of course) and then call an in-place copy constructor. That would cover more use cases than the current mechanism of simply assigning each member variable in a 1-to-1 fashion. (That would be the copy constructor's default implementation.)
Are there situations I am overlooking that the current C++ standard would address that my proposed change would not? Methinks I am going to head this direction with K++. ^_^
This has crossed my mind due to my recent interest in language design. It would be so much nicer being able to simply define the copy constructor, move constructor, and the destructor. The = operator would (if not defined otherwise) simply call an in-place destructor (not release the memory, of course) and then call an in-place copy constructor. That would cover more use cases than the current mechanism of simply assigning each member variable in a 1-to-1 fashion. (That would be the copy constructor's default implementation.)
Are there situations I am overlooking that the current C++ standard would address that my proposed change would not? Methinks I am going to head this direction with K++. ^_^
- dandymcgee
- ES Beta Backer
- Posts: 4709
- Joined: Tue Apr 29, 2008 3:24 pm
- Current Project: https://github.com/dbechrd/RicoTech
- Favorite Gaming Platforms: NES, Sega Genesis, PS2, PC
- Programming Language of Choice: C
- Location: San Francisco
- Contact:
Re: C++ default operator= implementation
I've read this a few times and still don't really understand what you're trying to say. Do you have a code example?
Falco Girgis wrote:It is imperative that I can broadcast my narcissistic commit strings to the Twitter! Tweet Tweet, bitches!
- Nokurn
- Chaos Rift Regular
- Posts: 164
- Joined: Mon Jan 31, 2011 12:08 pm
- Favorite Gaming Platforms: PC, SNES, Dreamcast, PS2, N64
- Programming Language of Choice: Proper C++
- Location: Southern California
- Contact:
Re: C++ default operator= implementation
I do not think you are understanding the distinction between initialization and assignment, whether it be by copy or move.TheBuzzSaw wrote:The more I work in C++, the more I've noticed a theme in my object design. To avoid repeating code, I often make a copy function and a destroy function. My copy constructor calls the copy function. My destructor calls the destroy function. My operator overload calls destroy followed by copy. Would this not be a superior default implementation?
This has crossed my mind due to my recent interest in language design. It would be so much nicer being able to simply define the copy constructor, move constructor, and the destructor. The = operator would (if not defined otherwise) simply call an in-place destructor (not release the memory, of course) and then call an in-place copy constructor. That would cover more use cases than the current mechanism of simply assigning each member variable in a 1-to-1 fashion. (That would be the copy constructor's default implementation.)
Are there situations I am overlooking that the current C++ standard would address that my proposed change would not? Methinks I am going to head this direction with K++. ^_^
Let's say you do this:
class foo { public: std::string bar; int baz; }; foo f;f is instantiated on the stack and then constructed using the implementation-provided default constructor, which default-constructs all foo class members. The std::string default constructor initializes an empty string. The int default constructor is nop, as int is a plain-old-data type. This is simple. Let's add a copy constructor and copy assignment operator that uses your idea.
class foo { public: std::string bar; int baz; foo() { } foo(const foo& other) { copy(other); } foo& operator=(const foo& other) { copy(other); return *this; } void copy(const foo& other) { bar = other.bar; baz = other.baz; } }; foo f; foo g = f;Now, we have a problem. f is being default-constructed, then g is being assigned to the copy of f. By using the assignment operator to initialize g, however, a compiler without move semantics will do this:
foo f; foo g; g = f;This means that g is being default-constructed before being assigned. You're doing twice as much work by initializing the string member bar and then overwriting it with another string! This is no big deal with a C++11 compiler, but let's say we're using C++03. The obvious solution, then, is to switch from assignment to initialization:
foo f; foo g(f);Now we are using the copy constructor. This, however, is doing the same thing. The constructor is still initializing all class members not in the constructor's initializer list, before calling the copy method, so we haven't really done anything. What you are supposed to do is use member initialization:
foo(const foo& other) : bar(other.bar), baz(other.baz) { }This prevents the compiler from automatically initializing bar and baz, and instead uses your own initialization.
As for your destroy function, I try to avoid putting myself in a situation where having zombie objects is normal. I prefer to tightly control the lifetime of the objects themselves, rather than the lifetime of their members, as I feel that this it the natural thing to do in C++. It's not necessary to use a destroy function unless you are using inheritance, as you can call destructors directly:
f.~foo();However, if I see you doing this in any of your code, I will very quickly get the fuck out of there.
Also, move semantics in C++11 can help with many of the problems you are having, I think, by distinguishing between creating a true copy of an object and moving its data to another instance.
Edit: I am very tired right now, I might've misunderstood or explained something poorly. If you ask for clarification I will provide it when I am properly awake.
- bbguimaraes
- Chaos Rift Junior
- Posts: 294
- Joined: Wed Apr 11, 2012 4:34 pm
- Programming Language of Choice: c++
- Location: Brazil
- Contact:
Re: C++ default operator= implementation
I'm pretty sure
P.S.: yeah, just tested it:
Foo g = f;is still using the copy constructor. The rest of the post is right on, though.
P.S.: yeah, just tested it:
class C { public: C() {} C(const C & other); C & operator=(const C & other) {} }; int main() { C c; C d = c; }
$ g++ --version; g++ -o test test.cpp g++ (Ubuntu/Linaro 4.6.4-1ubuntu1~12.04) 4.6.4 /tmp/ccn1INuJ.o: In function `main': test.cpp:(.text+0x23): undefined reference to `C::C(C const&)' collect2: ld returned 1 exit status
- TheBuzzSaw
- Chaos Rift Junior
- Posts: 310
- Joined: Wed Dec 02, 2009 3:55 pm
- Current Project: Paroxysm
- Favorite Gaming Platforms: PC
- Programming Language of Choice: C++
- Contact:
Re: C++ default operator= implementation
@Nokurn, as was pointed out, you are incorrect.
That is just syntactic sugar to invoke the copy constructor.
And trust me: I understand how initialization and assignment work in C++.
From there, I think you missed the point of my post. I am referring strictly to the compiler-provided default implementation. My point is that it provides an erroneous construct. I do not build my copy/destroy functions in all my objects. I was merely observing that it is a common pattern. Here is my point:
So far, so good, right? The problem is that the default implementation does a member by member copy.
Now we're hosed because the block will be double freed. My point is that a better default implementation would be this:
While not optimal, it is infinitely more correct.
The same applies to the equivalent move semantics.
Code: Select all
Foo f = g;
And trust me: I understand how initialization and assignment work in C++.
From there, I think you missed the point of my post. I am referring strictly to the compiler-provided default implementation. My point is that it provides an erroneous construct. I do not build my copy/destroy functions in all my objects. I was merely observing that it is a common pattern. Here is my point:
Code: Select all
class Stuff
{
public:
Stuff(size_t size)
{
block = malloc(size);
}
Stuff(const Stuff& other)
{
size = other.size;
block = malloc(size);
memcpy(block, other.block, size);
}
~Stuff()
{
free(block);
}
private:
void* block;
size_t size;
};
Code: Select all
Stuff& operator=(const Stuff& other)
{
block = other.block;
size = other.size;
return *this;
}
Code: Select all
Stuff& operator=(const Stuff& other)
{
~Stuff();
new (this) Stuff(other);
return *this;
}
The same applies to the equivalent move semantics.
- Falco Girgis
- Elysian Shadows Team
- Posts: 10294
- Joined: Thu May 20, 2004 2:04 pm
- Current Project: Elysian Shadows
- Favorite Gaming Platforms: Dreamcast, SNES, NES
- Programming Language of Choice: C/++
- Location: Studio Vorbis, AL
- Contact:
Re: C++ default operator= implementation
Just a few things, gentlemen...
TheBuzzSaw, I see what you're saying... That actually does kinda make sense to me... and the default, compiler-generated copy constructor and default constructors would still make this work as expected for the normal scenarios...
I'm just being anal, but if you're actually declaring that at global scope, f is being allocated in the data segment, not the stack.Nokurn wrote:Let's say you do this:class foo { public: std::string bar; int baz; }; foo f;f is instantiated on the stack and then constructed using the implementation-provided default constructor,
Move semantics only apply to temporary variables (r-values), so the scenario of initializing one variable from an existing variable would not even use move semantics in C++11.Nokurn wrote:This means that g is being default-constructed before being assigned. You're doing twice as much work by initializing the string member bar and then overwriting it with another string! This is no big deal with a C++11 compiler, but let's say we're using C++03. The obvious solution, then, is to switch from assignment to initialization:
The placement destructor is actually a very useful construct when you're managing your own memory (especially in embedded environments)... You use placement new to initialize an existing address then use the placement delete to uninitialize the object without actually freeing the memory.Nokurn wrote: It's not necessary to use a destroy function unless you are using inheritance, as you can call destructors directly:f.~foo();However, if I see you doing this in any of your code, I will very quickly get the fuck out of there.
TheBuzzSaw, I see what you're saying... That actually does kinda make sense to me... and the default, compiler-generated copy constructor and default constructors would still make this work as expected for the normal scenarios...
- MarauderIIC
- Respected Programmer
- Posts: 3406
- Joined: Sat Jul 10, 2004 3:05 pm
- Location: Maryland, USA
Re: C++ default operator= implementation
I've never had a need to do placement new, but why not do it this way?Falco Girgis wrote:The placement destructor is actually a very useful construct when you're managing your own memory (especially in embedded environments)... You use placement new to initialize an existing address then use the placement delete to uninitialize the object without actually freeing the memory.Nokurn wrote: It's not necessary to use a destroy function unless you are using inheritance, as you can call destructors directly:f.~foo();However, if I see you doing this in any of your code, I will very quickly get the fuck out of there.
int main() { { Foo* foo = new(0x1234) Foo(); foo->doStuff(); } /* Yay, destructor */ }
I realized the moment I fell into the fissure that the book would not be destroyed as I had planned.
- Falco Girgis
- Elysian Shadows Team
- Posts: 10294
- Joined: Thu May 20, 2004 2:04 pm
- Current Project: Elysian Shadows
- Favorite Gaming Platforms: Dreamcast, SNES, NES
- Programming Language of Choice: C/++
- Location: Studio Vorbis, AL
- Contact:
Re: C++ default operator= implementation
^ that will not invoke the destructor. Your extra scope there will remove the pointer from the stack, not destroy what it's pointing to. That's a memory leak.
- MarauderIIC
- Respected Programmer
- Posts: 3406
- Joined: Sat Jul 10, 2004 3:05 pm
- Location: Maryland, USA
Re: C++ default operator= implementation
I thought it looked like one, derp. I brainfarted and combined his non-pointer syntax with your stuff about placement new. embarrassing
I realized the moment I fell into the fissure that the book would not be destroyed as I had planned.
- Falco Girgis
- Elysian Shadows Team
- Posts: 10294
- Joined: Thu May 20, 2004 2:04 pm
- Current Project: Elysian Shadows
- Favorite Gaming Platforms: Dreamcast, SNES, NES
- Programming Language of Choice: C/++
- Location: Studio Vorbis, AL
- Contact:
Re: C++ default operator= implementation
lol you're fine. For a minute I thought that was legit too.MarauderIIC wrote:I thought it looked like one, derp. I brainfarted and combined his non-pointer syntax with your stuff about placement new. embarrassing
- Nokurn
- Chaos Rift Regular
- Posts: 164
- Joined: Mon Jan 31, 2011 12:08 pm
- Favorite Gaming Platforms: PC, SNES, Dreamcast, PS2, N64
- Programming Language of Choice: Proper C++
- Location: Southern California
- Contact:
Re: C++ default operator= implementation
Rereading this thread, I can see that I shouldn't have been posting after running a donation car wash in the Mojave Desert for 8 hours during July.
I can see the merits of your suggestion, but my main qualm with this implementation is that it assumes that copy-assignment requires full destruction. It also assumes that copy-construction and copy-assignment are semantically equivalent, but that complaint has fewer consequences. I think your idea would mainly benefit people who write low-level classes. Those writing classes on the application level wouldn't see any real use for this. If your class consists only of object members (no raw pointers, no references), the standard default copy-constructor works perfectly, generating calls to the members' copy constructors, which may or may not destruct before copying, depending on the member's requirements, which are up to the author to determine and implement.
I can see the merits of your suggestion, but my main qualm with this implementation is that it assumes that copy-assignment requires full destruction. It also assumes that copy-construction and copy-assignment are semantically equivalent, but that complaint has fewer consequences. I think your idea would mainly benefit people who write low-level classes. Those writing classes on the application level wouldn't see any real use for this. If your class consists only of object members (no raw pointers, no references), the standard default copy-constructor works perfectly, generating calls to the members' copy constructors, which may or may not destruct before copying, depending on the member's requirements, which are up to the author to determine and implement.
- TheBuzzSaw
- Chaos Rift Junior
- Posts: 310
- Joined: Wed Dec 02, 2009 3:55 pm
- Current Project: Paroxysm
- Favorite Gaming Platforms: PC
- Programming Language of Choice: C++
- Contact:
Re: C++ default operator= implementation
This highlights an important point. There are two fundamental categories of class design: one where the members are self-contained (where direct assignment is safe) and one where the members represent something outside itself (where direct assignment is deadly). The reason I proposed the new implementation was to make sure that all cases would work in the absence of a superior implementation.Nokurn wrote:Rereading this thread, I can see that I shouldn't have been posting after running a donation car wash in the Mojave Desert for 8 hours during July.
I can see the merits of your suggestion, but my main qualm with this implementation is that it assumes that copy-assignment requires full destruction. It also assumes that copy-construction and copy-assignment are semantically equivalent, but that complaint has fewer consequences. I think your idea would mainly benefit people who write low-level classes. Those writing classes on the application level wouldn't see any real use for this. If your class consists only of object members (no raw pointers, no references), the standard default copy-constructor works perfectly, generating calls to the members' copy constructors, which may or may not destruct before copying, depending on the member's requirements, which are up to the author to determine and implement.
- Nokurn
- Chaos Rift Regular
- Posts: 164
- Joined: Mon Jan 31, 2011 12:08 pm
- Favorite Gaming Platforms: PC, SNES, Dreamcast, PS2, N64
- Programming Language of Choice: Proper C++
- Location: Southern California
- Contact:
Re: C++ default operator= implementation
I can understand that, but I wouldn't be able to justify the amount of overhead that such a default implementation would incur. Of course, I can't make these claims with any certainty, but I would think there is more application-level code than low-level code in production. While the low-level classes that would benefit from this are likely already doing something similar, the higher level classes that use default-generated copy-assignment operators would suffer some overhead from explicitly calling all member destructors prior to copying, because everything would essentially be destroyed twice (once in the destructor, once in the low-level copy).TheBuzzSaw wrote:This highlights an important point. There are two fundamental categories of class design: one where the members are self-contained (where direct assignment is safe) and one where the members represent something outside itself (where direct assignment is deadly). The reason I proposed the new implementation was to make sure that all cases would work in the absence of a superior implementation.Nokurn wrote:Rereading this thread, I can see that I shouldn't have been posting after running a donation car wash in the Mojave Desert for 8 hours during July.
I can see the merits of your suggestion, but my main qualm with this implementation is that it assumes that copy-assignment requires full destruction. It also assumes that copy-construction and copy-assignment are semantically equivalent, but that complaint has fewer consequences. I think your idea would mainly benefit people who write low-level classes. Those writing classes on the application level wouldn't see any real use for this. If your class consists only of object members (no raw pointers, no references), the standard default copy-constructor works perfectly, generating calls to the members' copy constructors, which may or may not destruct before copying, depending on the member's requirements, which are up to the author to determine and implement.
If C++ were a less general purpose language and more of a systems language, I think this would make a fine default implementation, provided that there would be no need for copy-construction and copy-assignment to be semantically distinct operations.