0

Let Memeber be a class and let's assume I have no idea if it supports or need move semantics. For all intents and purposes let's say it isn't even specified/written yet. Which case of the SetMember functions should I implement in an Outer class, which has Member as a member?

If Member would not support move, I would do this:

class Member {
public:
    Member(Member&&) = delete;
    Member& operator=(Member&&) = delete;
};

class Outer {
    Member m_member;
public:
    void SetMember(const Member& o) { m_member = o.m_member; } // makes a copy
};

And if Member would support move, I would do this:

class Member {
public:
    Member(Member&&);
    Member& operator=(Member&&);
};

class Outer {
    Member m_member;
public:
    void SetMember(Member o) { m_member = std::move(o.m_member); } // makes a copy if needed
};

But since I do not know if it has move or not, do I need to implement both? Like this:

class Outer {
    Member m_member;
public:
    void SetMember(const Member& o) { m_member = o.m_member; } // makes a copy
    void SetMember(Member&& o) { m_member = std::move(o.m_member); } // makes no copy
};

Or should I do this?

class Outer {
    Member m_member;
public:
    template <class T>
    void SetMember(T&& o) { m_member = std::forward<T>(o.m_member); }
};

Why I'm not happy with these two solutions:

  • In the first solution I see code duplication, which is only needed because I don't know some implementation details of Member namely if it supports move or not.
  • The second solution leaves me with compilation errors instead of intelli sense errors whenever I try to use SetMember on a wrong type. Also I need a template just because some implementation details of Member.

What's the clean way to handle this situation?

Dominic Hofer
  • 5,751
  • 4
  • 18
  • 23
  • my vote is for perfect forwarding – wtom Jul 02 '18 at 12:26
  • Does the setter ever do anything besides setting the value? Any checks? If not, you have a glorified public field. Might as well cut the middle man. – StoryTeller - Unslander Monica Jul 02 '18 at 13:03
  • 1
    @StoryTeller It's a glorified public field *at present*. The benefit of having a setter is that you can add extra complexity later without changing the class's interface. – Fibbs Jul 02 '18 at 14:14
  • @Fibbles - Aha, because client code that suddenly breaks is something we all love. The more uses this gets, the more likely a glorified public field it remains. I'd ponder what the OP is encapsulating here, before they assume encapsulation equates to setters. – StoryTeller - Unslander Monica Jul 02 '18 at 14:23
  • @StoryTeller So long as the promises of the interface don't change (which they shouldn't) client code should function the same. I suppose it's a matter of opinion but I feel that if you have an exposed member then there's no downside to the extra abstraction of a setter (other than writing the initial boilerplate). They do however offer freedom to modify the implementation of the class later on because they strengthen encapsulation. It's the mindless creation of setters for every member, regardless of need, that harms encapsulation. I realise this is off-topic though so I'll stop here. – Fibbs Jul 02 '18 at 15:34
  • @Fibbles - Unconstrained setting of the value is part of the interface. So you can't add any checks anymore. I mostly object to modeling types by what they posses instead of what they do. You may say it's reasonable to model a type that just holds values, but that's by definition an aggregate. And aggregates should not have boiler-plate. But we indeed digress. – StoryTeller - Unslander Monica Jul 02 '18 at 15:47

1 Answers1

0

As far as I know passing by value in setters is no longer recommended. At least in case of std::strings when setter is called multiple times, it is highly probable, that the destination variable has enough memory reserved for new value and it's cheaper to just copy the content into already allocated string then create a temporary string and move it.
So no matter if the type is moveable or not it is recommended to pass by const reference and make a copy inside of a setter. If later profiling shows that a temporary is used often as an argument and the cost of copying makes it worth optimizing, an overload for rvalue reference may be added.
See also https://stackoverflow.com/a/26286741/113662

Tadeusz Kopec for Ukraine
  • 12,283
  • 6
  • 56
  • 83