【问题标题】:How to avoid this kind of repetition如何避免这种重复
【发布时间】:2016-09-30 09:11:30
【问题描述】:

我有类似这样的代码:

#include <string>

class A{
public:
    std::string &get(){
        return s;
    }

    const std::string &get() const{
        return s;
    }

    std::string &get_def(std::string &def){
        return ! s.empty() ? s : def;
    }

    // I know this might return temporary
    const std::string &get_def(const std::string &def) const{
        return ! s.empty() ? s : def;
    }

private:
    std::string s = "Hello";
};

我想知道有没有简单的方法可以避免 get() 函数中的代码重复?

【问题讨论】:

  • 烦人吧?我很想用 const 或什么都没有创建一个宏,但它不像 C++。
  • 我什至没有办法重用没有 const_cast 的函数或使 s 可变
  • Ew @ 右对齐 & 符号 ;)
  • 欺骗*.com/q/2150192/560648?虽然那个Q有点垃圾……
  • @LightnessRacesinOrbit - 正确答案在这里:*.com/questions/123758/…

标签: c++ c++11 dry


【解决方案1】:

wandbox example

替代const_cast:创建一个以*this 为参考的static 模板函数:

class A
{
private:
    template <typename TSelf, typename TStr>
    static auto& get_def_impl(TSelf& self, TStr& def)
    {
        return !self.s.empty() ? self.s : def;
    }

public:
    auto& get_def(std::string& str)
    {
        return get_def_impl(*this, str);
    }

    const auto& get_def(const std::string& str) const
    {
        return get_def_impl(*this, str);
    }
};

这是因为 template argument deduction rules 有效 - 简而言之,TSelf 将接受 const 和非 const 引用。

如果您需要在get_def_impl 中访问this 的成员,请使用self.member

此外,您可以使用std::conditionalget_def_impl 中的类似设施来执行不同的操作,具体取决于TSelfconst-ness。您还可以使用转发引用 (TSelf&amp;&amp;) 并处理由于 ref-qualifiersperfect-forwarding 而移动 this 的情况。

【讨论】:

  • 你为什么要移动*this(我想这就是你的意思)?
  • @LightnessRacesinOrbit:我的意思是处理从临时A 实例(auto get_def(/*...*/) &amp;&amp; { /*...*/ }) 调用get_def 的情况。在这种情况下,您可能需要将 A 的一些成员移动到 get_def_impl
  • 你能举个例子吗?
  • @LightnessRacesinOrbit: this is very contrived 并且可能有更好的方法来执行此操作,但我们假设您有两个 static 重复帮助器和两组 ref 限定访问器。如果我调用 A{}.get() 并且我希望调用 both &amp;&amp; 限定版本的访问器而不需要任何代码重复或不必要的移动,我需要处理 rvalueness *this 中的 static 助手。如果您可以将其简化为不使用 *this 的转发引用,那就太好了。
  • *this 始终是一个左值。它代表一个临时对象并不重要。但这正是您需要std::move 的原因:)
【解决方案2】:

在某些用例中,您还可以使用非成员函数模板,例如:

#include <type_traits>
#include <string>

template <class U, class R = std::conditional_t<std::is_const<U>::value, std::string const&, std::string& >>
R get(U &u) {
   return u.s;
}

template <class U, class R = std::conditional_t<std::is_const<U>::value, std::string const&, std::string& >>
R get_def(U &u, typename std::remove_reference<R>::type& def) {
   return u.s.empty() ? u.s : def;
}

struct S {
   template <class U, class R>
   friend R get(U &);
   template <class U, class R>
   friend R get_def(U &, typename std::remove_reference<R>::type&);
private:
   std::string s;
};

int main() {
   S s;
   get(s) = "abc";
   //get(static_cast<const S &>(s)) = "abc"; // error: passing ‘const std::basic_string<char>’ as ‘this’...
   std::string s2 = get(static_cast<const S&>(s));
}

【讨论】:

    【解决方案3】:

    不直接回答问题,但我通常倾向于使用 const getter + non const setter - 这样您的班级会在字符串更改时收到通知,并且可以在需要时对其进行操作(将来) - 无需经历和改变所有使用它的东西。

    【讨论】:

    • 并不总是合适的。例如,假设std::vector::operator[]const-only,而您必须通过 setter 批量传入新值。
    • 我同意——并非总是如此;我承认这确实是一种 java/c# 的思维方式,但它在某些地方有它的优势。