【问题标题】:Universal member-function definition, instanceable from both 'const' & 'non-const' objects通用成员函数定义,可从 'const' 和 'non-const' 对象实例化
【发布时间】:2015-01-20 21:02:08
【问题描述】:

有没有办法定义一个可以从“const”和“non-const”对象访问的成员函数?

我的sList 实现列表类需要这个。在其中,我想声明一个函数,该函数将另一个函数作为参数,该函数具有指向sList 的“const”或“non-const”指针,并将为当前sList 结构中的每个列表调用它。

这是它的声明:

template <typename T>
struct sList
{
    sList(initializer_list<T>);

    inline void DoForEachList(auto pFunc)
    {
        for(auto p = this; p; p = p->pNext)
            pFunc(p);
    }

    ~sList();

    T dat;

    sList *pNext = nullptr;
};

我使用 auto pFunc 是因为我最终也想通过 lambda。所以现在,如果我有一个这种类型的 const 对象并从中调用“DoForEachList”作为参数 lambda 函数传递,其中 1 个 arg 来自“auto”类型。我的编译器将失败,如下所示:

错误:将const sList&lt;unsigned char&gt; 作为this 参数传递给void sList&lt;T&gt;::DoForEachList(auto:1) [with auto:1 = main()::&lt;lambda(sList&lt;unsigned char&gt;*)&gt;; T = unsigned char]' 丢弃限定符 [-fpermissive]

以及调用DoForEachList的代码:

void main()
{
    extern const sList<unsigned char> cvobj;
    cvobj.DoForEachList([] (auto pCurr) {/* Do something */});
}

有什么方法可以像这样定义DoForEachList 成员函数(或成员函数的模板):

template <typename T>
struct sList
{
    inline void DoForEachList(auto pFunc) auto //either 'const' or none
    {
        for(auto p = this; p; p = pNext->pNext)
            pFunc(p);
    }

    //...
};

【问题讨论】:

  • 您可以使用友元函数模板,也可以使用两个成员函数包装器。
  • 非常简单 - 非常量对象始终可以绑定到 const 引用,但反之则不行。这是一个隐式转换(限定转换)。嗯。但是,如果您在非常量对象上调用它时确实想修改该对象,那将毫无用处。在这种情况下,只需编写两个函数即可。
  • 同一个身体——呃,这是最好的方法吗?使用包装函数不是一个解决方案,因为这样会错误地推断出 'pFunc' 参数(如果我们使用 const_cast 为 'this' 在 'const' 成员函数中包装 'non-const' 调用,则 lambda 函数 arg 将被推断为 ' sList *' 这是错误的)。
  • 如果我们以相反的方式包装(在'non-const'成员函数中调用'const'),也会发生同样的情况。这样 lambda 函数 arg 将被推导出为 'const sList *'。
  • 我喜欢 dyp 的解决方案。请记住,friend 函数不是成员函数,也没有 this。相反,它将是从两个成员函数包装器传递的普通旧参数。你不需要const_cast。朋友模板的实例之一将具有const-qualified 类型参数。另一个实例不会。它们都是 const 正确的。

标签: c++ c++11 gcc c++14 mingw-w64


【解决方案1】:

以@dyp 在 cmets 中的回答为基础:

如果你想重载this 的常量,你确实需要两个单独的函数。但是,您可以通过将工作卸载到辅助函数来最大程度地减少重复。

@dyp 建议为此使用友元函数模板,但友元函数没有访问控制,所以我通常更喜欢静态成员函数;然后,您可以将其设为私有或受保护:

template <typename T>
struct sList
{
    void DoForEachList(auto pFunc)
    {
        DoForEachListHelper(*this, pFunc);
    }
    void DoForEachList(auto pFunc) const
    {
        DoForEachListHelper(*this, pFunc);
    }
private:
    static void DoForEachListHelper(auto&& self, auto pFunc)
    {
        for(auto p = &self; p; p = pNext->pNext)
            pFunc(p);
    }
};

【讨论】:

  • @hvd 谢谢。使用auto&amp;&amp;,您还可以在引用限定符上重载DoForEachList,但这并不是说在这种情况下会有很大的不同。
  • 是的,当我注意到它可能是auto self 时,这是我的第一个建议,self 将是一个指针,在这种情况下&amp;&amp; 根本没有用。在这种特殊情况下,self 是一个参考,它仍然没有什么区别,因为你只用左值调用它,但它是一种简单而有用的通用方法,我可以理解你为什么选择它。 :)
  • “朋友功能没有访问控制。”我不明白你在这里想说什么。
  • @dyp 如果你声明一个朋友函数,任何人都可以调用它,对吧?
  • @hvd 啊,是的。有一些方法可以模拟私人朋友函数,但它们当然不如直接使用访问说明符那么优雅。
【解决方案2】:

您应该只使用const 成员函数并创建成员变量mutable,它告诉编译器/您该成员不会影响类的“已更改”行为。如果不是这种情况,请重新考虑您的设计,因为这不太正确。 mutable 更正确,例如地图的键需要以不影响该地图中的顺序的方式进行修改,但即便如此......

代码示例:

struct Bla
{
    void increment() const { i++; }
private:
    mutable int i = 0;
};

int main()
{
    const Bla bla;
    bla.increment();
}

Live demo here.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-17
    • 2019-04-01
    • 1970-01-01
    • 2021-05-20
    • 1970-01-01
    相关资源
    最近更新 更多