0

In an effort to understand the move operator when dealing with a member that is an array, I wrote the following development test.

Why does this work and/or compile when I've violated the rule of 5 by not implementing an assignment or a move assignment operator?

I am currently using Visual Studio 2015.

#include <cstring>
#include <iostream>
#include <string>

struct Foo
{
    Foo(const char * cstring, const std::string & string)
        :
        m_string(string)
    {
        strncpy_s(m_cstring, sizeof(m_cstring), cstring, _TRUNCATE);
    }

    Foo(const Foo & rhs)
        :
        m_string(rhs.m_string)
    {
        std::copy(rhs.m_cstring, rhs.m_cstring + sizeof(rhs.m_cstring), m_cstring);
    }

    Foo(Foo && rhs)
        :
        m_string(std::move(rhs.m_string))
    {
        std::copy(rhs.m_cstring, rhs.m_cstring + sizeof(rhs.m_cstring), m_cstring);
    }

    char        m_cstring[6];
    std::string m_string;
};

int main()
{
    // Initialize
    Foo a("Hello", "World");

    // Copy constructor
    Foo b(a);

    std::cout << &a.m_cstring << " " << a.m_cstring << " " << a.m_string << std::endl;
    std::cout << &b.m_cstring << " " << b.m_cstring << " " << b.m_string << std::endl << std::endl;

    // Move constructor
    Foo c(std::move(a));

    std::cout << &c.m_cstring << " " << c.m_cstring << " " << c.m_string << std::endl << std::endl;

    // Assignment operator
    Foo d = c;

    std::cout << &c.m_cstring << " " << c.m_cstring << " " << c.m_string << std::endl;
    std::cout << &d.m_cstring << " " << d.m_cstring << " " << d.m_string << std::endl << std::endl;

    // Move Assignment operator
    Foo e = std::move(c);

    std::cout << &e.m_cstring << " " << e.m_cstring << " " << e.m_string << std::endl << std::endl;

    return 0;
}
Christopher Pisz
  • 3,757
  • 4
  • 29
  • 65
  • 6
    Afaik, the rule of 5 doesn't define what's compilable, it defines what's needed for safe memory management. – Carcigenicate Nov 27 '17 at 23:06
  • 2
    And in this case, the compiler already knows how to copy an array of primitive types, and `std::string` is rule-of-5 compliant, so you don't need to implement copy/move operators manually, the default compiler generated implementations will suffice. – Remy Lebeau Nov 27 '17 at 23:08
  • What has the compiler generated for the copying of the array? std::copy? – Christopher Pisz Nov 27 '17 at 23:09
  • 1
    @ChristopherPisz: The compiler generated operators perform a member-by-member copy, and in the case of an array member, an element-by-element copy. It does not use `std::copy()` itself – Remy Lebeau Nov 27 '17 at 23:09
  • @user4581301 I am not a believer in the rule of zero, because I have had to go and set breakpoints and debug when a particular class' constructor took place more times then I can count. I'd rather just always knock them out up front then have to add them every debugging session and risk non compatibility with a particular crash dump. – Christopher Pisz Nov 27 '17 at 23:10
  • Ok , ty sirs. If anyone wants to summize this info in an answer, I can hand out the checky mark. – Christopher Pisz Nov 27 '17 at 23:12
  • 2
    @Christopher It's very easy to implement the constructors etc. wrong. Don't do it if you don't need to. –  Nov 27 '17 at 23:13
  • 1
    After you do the `std::move(a)`, it's bad to continue using `a`. – Eljay Nov 27 '17 at 23:14
  • @Neil Well, again, what would you suggest I do, if I abide by that rule, when it comes time to debug a crash dump, and I need to know when a particular class is being constructed and its address? Rebuilding with a breakable line of code invalidates compatibility with a dump. – Christopher Pisz Nov 27 '17 at 23:14
  • @Eljay Good point! I will fix the listing – Christopher Pisz Nov 27 '17 at 23:14
  • @Neil that's my point, right? If I don't implement the 5 methods myself, even when they are trivial, it causes me real world grief when it comes time to debug a crash dump. However, everyone is quick to point out the rule of zero in any code review, and elsewhere. Yet, I can't get good answer to what to do about the debugging scenario if I was to abide by the rule of zero, so I rebel and refuse to conform, at least until there is a good answer to it. – Christopher Pisz Nov 27 '17 at 23:21
  • 1
    Sorry, I misread your comment - it is a good rule - you re-implementing these methods that won't crash because they use some of the most heavily tested code on the planet is a bad idea. –  Nov 27 '17 at 23:24
  • @ChristopherPisz: None of your "assignment" examples actually use the assignment operator. Those are simply copy initialization, which will call *constructors*. – Nicol Bolas Nov 27 '17 at 23:24
  • Have a look at [the rule of three/five/zero](http://en.cppreference.com/w/cpp/language/rule_of_three). The rule of three/five is about manual handling of dynamic resources. Neither your static array nor `std::string` is a dynamic resource that requires manual handling. The resource handling of `std::string` is implicit in the class and the resource handling of `m_cstring` is implicitly handled by the compiler. – Pixelchemist Nov 27 '17 at 23:25
  • @neil Then my question remains. What are you going to do, when you have not implemented these methods yourself, and it comes time to debug a crash dump, where you must answer the question, "When was this class instantiated and what is its address?", when you have no line to break on? If you can answer me that. I will gladly be converted! – Christopher Pisz Nov 27 '17 at 23:26
  • @pixel I am aware. Read the ongoing discussion about the rule of zero and debugging problems. I suppose I could reopen a new question specifically about that. – Christopher Pisz Nov 27 '17 at 23:27
  • New question opened here that is more specific to the discussion about the rule of zero and the debugging scenario I am arguing: https://stackoverflow.com/questions/47521689/if-you-follow-the-rule-of-zero-how-will-you-debug-the-construction-of-an-object – Christopher Pisz Nov 27 '17 at 23:48
  • 1
    The only counter argument is I haven't had the problem you describe often enough to consider it a problem worth inserting another entire family of problems into a codebase to make it easier to debug. I'm much more likely to delete constructors and operators to see where I am accidentally invoking a copy or move than I am to pop a breakpoint in one. – user4581301 Nov 27 '17 at 23:50
  • @user4581301 Fair enough. I've had the opposite experience and therefore the opposite philosophy. it was not an enjoyable experience having to explain to my superiors that we had to deploy another build, reproduce the production crash, and then retrieve another dump, just so I could debug the issue, and that experience has happened more than a few times now. Probably because the legacy code dumped in my lap is crappier than your code :) – Christopher Pisz Nov 27 '17 at 23:52

0 Answers0