【问题标题】:Why are (member) function pointers behaving so weirdly in Visual C++?为什么(成员)函数指针在 Visual C++ 中表现得如此怪异?
【发布时间】:2012-12-20 00:34:00
【问题描述】:

我遇到了一个非常奇怪的问题,我将其简化为以下测试用例:

#include <iostream>
#include <map>
#include <string>

struct Test
{
    std::map<std::string, void (Test::*)()> m;
    Test()
    {
        this->m["test1"] = &Test::test1;
        this->m["test2"] = &Test::test2;
    }
    void test1() { }
    void test2() { }
    void dispatch(std::string s)
    {
        if (this->m.at(s) == &Test::test1)
        { std::cout << "test1 will be called..." << std::endl; }
        else if (this->m.at(s) == &Test::test2)
        { std::cout << "test2 will be called..." << std::endl; }
        (this->*this->m.at(s))();
    }
};

int main()
{
    Test t;
    t.dispatch("test1");
    t.dispatch("test2");
}

输出

test1 将被调用...
test1 将被调用...

当启用优化时,这真的很奇怪。怎么回事?

【问题讨论】:

  • 以这种方式使用函数指针作为唯一标识“句柄”似乎有点不合时宜,正是出于这个原因。
  • @Mehrdad:因为问题是关于C++的,而且测试用例是用C++写的,而且C++和C是不同的语言,规则不同。
  • @LightnessRacesinOrbit 请保持建设性。 “我不关心任何想用谷歌搜索这个问题的人”和“现在改变它,但 IDK 改变它”听起来很自私。
  • @Mehrdad:我选择这句话并非没有先例。参见仙女座,其中 Trance 说“我不在乎骨头”,这并不意味着她对它们有明显的负面个人和情感依恋,而是她的经验和智慧并不重视它们,与声称相反附近的其他人认为这些骨头具有重要价值。同样,在这里我的意思只是,在我看来,正确标记问题比最大化搜索引擎点击更重要。否则,让我们也标记问题php、java 和mysql,让每个人 参与进来! =)
  • @JamesMcNellis: Done! 如果您发现他们发布了我在一天左右没有注意到的后续内容,请随时联系我。

标签: c++ visual-c++ linker


【解决方案1】:

这是 Visual C++ 称为相同 COMDAT 折叠 (ICF) 的副产品。它将相同的功能合并到一个实例中。您可以通过将以下开关添加到链接器命令行来禁用它:/OPT:NOICF(在 Visual Studio UI 中,它位于 Properties->Linker->Optimization->Enable COMDAT Folding)

您可以在此处的 MSDN 文章中找到详细信息:/OPT (Optimizations)

该开关是链接器阶段开关,这意味着您将无法仅针对特定模块或特定代码区域启用它(例如可用于编译器阶段优化的__pragma( optimize() ))。

然而,一般来说,依赖函数指针或文字字符串指针 (const char*) 来测试唯一性被认为是不好的做法。几乎所有 C/C++ 编译器都广泛实现了字符串折叠。函数折叠目前仅在 Visual C++ 上可用,尽管模板 元编程的广泛使用增加了将此功能添加到 gcc 和 clang 工具链的请求。

编辑:从 binutils 2.19 开始,包含的黄金链接器据说也支持 ICF,尽管我无法在我的本地 Ubuntu 12.10 安装上验证它。

【讨论】:

  • 然而,一般来说,依赖任何一个函数指针都被认为是不好的做法 ...我同意这一点,我不明白为什么有人需要依赖对此。查看更一般的讨论here
【解决方案2】:

事实证明,Visual C++ 的链接器可以将具有相同定义的函数合并为一个。
根据 C++ 是否合法,我不知道;它会影响可观察的行为,所以对我来说它看起来像一个错误。不过,其他有更多信息的人可能想插话。

【讨论】:

  • 或者,更准确地说,“实施者随意”。
  • 我在 5.3.1 中没有看到任何禁止这样做的内容。 &amp; 应该给你一个指向成员函数的指针,但它没有说明任何要求它必须是唯一的。
  • @Lightness Races in Orbit:这个笑话太容易预测了,不可能真正有趣:)
  • @AndreyT:我不是在开玩笑!
  • @Lightness Races in Orbit:我找不到 connect 问题,但这是我同时在 MS 论坛上发布的主题:social.msdn.microsoft.com/Forums/en/vclanguage/thread/…
【解决方案3】:

C++11 5.3.1 描述了&amp; 的作用;在这种情况下,它为您提供了一个指向相关成员函数的指针,并且文章没有要求这个指针必须是唯一的。

但是,5.10/1 说的是 ==:

两个相同类型的指针比较相等当且仅当它们都为空,都指向同一个函数,或者都代表同一个地址。

那么问题就变成了……test1 和test2“同一个功能”吗?

虽然优化器已将它们合并为一个定义,但可以说这两个名称标识了两个函数,因此,这似乎是一个实现错误。

(但请注意,VS 团队并不在意,并认为它“足够有效”以保证优化带来的好处。或者they don't realise that it's invalid。)

我会坚持使用字符串作为函数指针的“句柄”。

【讨论】:

  • +1,但我建议您使用 int 作为“句柄”,因为它们比较起来更快。
  • @Caesar:如果您考虑创建和维护对这些整数的查找的时间,则不会。
  • @Caesar:为什么不enum class?他们快而且他们有名字!
  • 它们都代表同一个地址。因此 Visual C++ 的优化仍然符合标准。
  • @jstine:也许您可以准确地阐明我误解的标准是什么。至于 VC++,它并非毫无根据——在我链接到的线程中,Passant 和他的同事明确声称该功能不是错误,即使它是。我的这些结论确实依赖于我没有犯错的观念;我当然对我的可能性持开放态度,但也许你可以为我识别它,而不是简单地尖叫“你错了”,投反对票并逃跑?
猜你喜欢
  • 2010-10-02
  • 2018-09-12
  • 2012-02-29
  • 2019-03-10
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多