C++:在覆盖已弃用的虚方法时出现弃用警告

4
我有一个纯虚类,其中有一个应该是 const 的纯虚方法,但不幸的是它不是。这个接口在一个库中,这个类被几个其他类继承,在不同的项目中。
我试图使这个方法成为 const ,而不会破坏兼容性(至少在一段时间内),但我找不到一种方法,在非const方法重载时生成警告。
以下是我目前能够制作的示例:
阶段0:改变之前。只有非const版本的Interface :: doSomething()方法存在,并且它是纯虚的。 阶段1:过渡期间。 Interface :: doSomething()方法既有const版本也有非const版本。它们都有一个默认实现,以允许旧式和新式实现(在这个阶段它们不能是纯虚的,因为每个继承的类只会重写其中一个)。const版本调用非const版本,以确保与旧实现的兼容性,非const版本断言,因为它永远不应该被调用。 阶段2:只有非const版本的Interface :: doSomething()方法存在,并且它是纯虚的。
在阶段1,我希望能够在类重写Interface::doSomething()的非const版本时产生警告,以警告用户他们应该更新他们的代码,这样当我切换到阶段2时,破坏其他人代码的机会非常低。不幸的是,我找不到一种方法来做到这一点。我尝试了GCC和Clang的几种标志组合,但唯一能做的就是使编译失败(例如将其更改为final),但这不是我想处理此问题的方式。有没有一种方法可以生成警告?
#include <iostream>
#include <cassert>

class Interface
{
public:
    virtual ~Interface() = default;

// callDoSomething method:
// - stage 0: non const
// - stage 1-2: const
#if (STAGE == 0)
    void callDoSomething() { doSomething(); }
#else
    void callDoSomething() const { doSomething(); }
#endif

protected:

// non-const doSomething() method:
// - stage 0: pure virtual
// - stage 1: virtual with assert in default implementation (should never be called)
// - stage 2: removed
#if (STAGE == 0)
    virtual void doSomething() = 0;
#elif (STAGE == 1)
    [[deprecated("Overload const version instead")]]
    virtual void doSomething()
    {
        assert(false);
    }
#endif

// const doSomething() method
// - stage 0: N/A
// - stage 1: virtual with default implementation (calls the non-const overload)
// - stage 2: pure virtual
#if (STAGE == 1)
    virtual void doSomething() const
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
        std::cout << "  calling non const version\n";
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
        const_cast<Interface*>(this)->doSomething();
#pragma GCC diagnostic pop
    }
#elif (STAGE == 2)
    virtual void doSomething() const = 0;
#endif
};


// Old style implementation: non-const doSomething()
// Allowed only in stages 0 and 1
#if (STAGE == 0 || STAGE == 1)
class Implementation_old : public Interface
{
public:
    virtual ~Implementation_old() = default;

protected:
    virtual void doSomething() override
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
    }
};
# endif


// Old style implementation: const doSomething()
// Allowed only in stages 1 and 2
#if (STAGE == 1 || STAGE == 2)
class Implementation_new : public Interface
{
public:
    virtual ~Implementation_new() = default;

protected:
    virtual void doSomething() const override
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
    }
};
#endif


int main(int argc, char *argv[])
{
    Interface* iface = nullptr;

#if (STAGE == 0 || STAGE == 1)
    iface = new Implementation_old;
    iface->callDoSomething();
    delete iface;
#endif

#if (STAGE == 1)
    std::cout << "-------------------\n";
#endif

#if (STAGE == 1 || STAGE == 2)
    iface = new Implementation_new;
    iface->callDoSomething();
    delete iface;
#endif

    iface = nullptr;

    return 0;
}

这是CMakeLists.txt文件,用于使用3个 STAGE 定义构建示例

cmake_minimum_required(VERSION 3.5)
project(test_deprecate_non_const)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(main_stage_0 main.cpp)
target_compile_definitions(main_stage_0 PRIVATE STAGE=0)

add_executable(main_stage_1 main.cpp)
target_compile_definitions(main_stage_1 PRIVATE STAGE=1)

add_executable(main_stage_2 main.cpp)
target_compile_definitions(main_stage_2 PRIVATE STAGE=2)

1
这超出了范围,但我很好奇,为什么您希望用户看到弃用警告?我想用户不必修改自己的代码以确保与新代码兼容,重新编译应该就足够了,对吧? - Oliv
最终,我进行了测试,在这里收到了弃用警告。 - Oliv
2
@Oliv,您的代码无论用户代码如何都会显示警告。这样,您将获得一个发出警告的库,而不管用户做什么。这是个坏事。思想是仅在用户代码覆盖非const方法时显示警告。 - bolov
1
也许你要找的是"覆盖"这个词,而不是"重载"。你的代码也远非最小化。 - n. m.
@Oliv 用户将不得不手动更新从接口继承的类中方法的签名,从非const到const,但除非他们阅读文档(但这不太可能发生)或收到警告,否则他们将不知道必须这样做。 - Daniele E. Domenichelli
显示剩余5条评论
1个回答

4
使用已弃用的接口时,最好能够发出警告。但是我的尝试以及你的尝试都失败了。我认为不幸的是,属性并没有考虑到这一点。我认为属性适用于实体的名称,这意味着仅在通过名称调用方法时才会收到警告。但我没有研究过这个标准。
所以,我很遗憾地从这篇答案中借鉴一个结论,它与本文略有关联:
告诉用户该函数已被弃用,不应再使用,然后继续前进。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接