在C++类成员函数上使用#ifdef保护是否安全?

假设您有以下 C++ 类的定义:

class A {
// Methods
#ifdef X
// Hidden methods in some translation units
#endif
};

这是否违反了班级的一个定义规则?有哪些相关危害?我怀疑如果使用成员函数指针或虚函数,这很可能会中断。否则使用安全吗?

我在 Objective C++ 的上下文中考虑它。头文件包含在纯 C++ 和 Objective C++ 翻译单元中。我的想法是使用 OBJC 宏保护具有 Objective-C 类型的方法。否则,我必须对标头中的所有 Objective-C 类型使用 void 指针,但这样我就失去了强类型,并且必须在整个代码中添加丑陋的静态强制转换。

回答

是的,如果允许单独的编译单元具有不同的宏定义状态,它可能会导致 ODR 违规的危险XX在每次包含程序的类定义之前,应该在程序(和共享对象)中全局定义(或不​​定义),以满足合规要求。就 C++ 编译器(不是预处理器)而言,它们是两种不同的、不兼容的、不相关的类类型

想象一下在编译单元A.cpp X之前定义class A并且在单元B.cpp X中未定义的情况。如果 B.cpp 中没有使用那些被“删除”的成员,你就不会得到任何编译器错误。两个单元本身都可以被认为是结构良好的。现在,如果B.cpp将包含一个新表达式,它将创建一个不兼容类型的对象,小于A.cpp 中定义的对象。但是class A,当使用在B.cpp 中创建的对象调用时,来自 的任何方法,包括构造函数,都可能通过访问对象存储之外的内存而导致 UB ,因为它们使用更大的定义。

这种愚蠢行为有一个变体,将头文件的副本包含到具有相同文件名和 POD 结构类型的构建树的两个或多个不同文件夹中,其中一个文件夹可以通过#include <filename>. #include "filename"设计为使用替代品的单位。但他们不会。因为在这种情况下头文件查找的顺序是平台定义的,程序员不能完全控制在每个平台上的哪个单元中包含哪个头文件#include "filename"。一旦一个定义被改变,即使只是通过重新排序成员,ODR 也会被破坏。

为了特别安全,这些事情应该只在编译器域中通过使用模板、PIMPL 等来完成。对于语言间通信,应该安排一些中间地带,使用包装器或适配器,C++ 和 ObjectiveC++ 可能具有非兼容的内存布局。 POD 对象。

  • @Peter-ReinstateMonica Except for the first `virtual` member function which adds vtable pointer to the object, right?
  • In all implementations known to mankind the number of member functions does not change the object size.
  • @R2RT Correct. I meant non-virtual functions (and for virtual ones your statement holds).

回答

这炸的很厉害。不要这样做。使用 gcc 的示例:

头文件:

// a.h

class Foo
{
public:
    Foo() { ; }

#ifdef A
    virtual void IsCalled();
#endif
    virtual void NotCalled();
};

第一个 C++ 文件:

// a1.cpp

#include <iostream>

#include "a.h"

void Foo::NotCalled()
{
    std::cout << "This function is never called" << std::endl;
}

extern Foo* getFoo();
extern void IsCalled(Foo *f);

int main()
{
   Foo* f = getFoo();
   IsCalled(f);
}

第二个 C++ 文件:

// a2.cpp

#define A
#include "a.h"
#include <iostream>

void Foo::IsCalled(void)
{
    std::cout << "We call this function, but ...?!" << std::endl;
}

void IsCalled(Foo *f)
{
    f->IsCalled();
}

Foo* getFoo()
{
    return new Foo();
}

结果:

这个函数永远不会被调用

哎呀!代码调用虚函数IsCalled,我们分派到,NotCalled因为两个翻译单元在类虚函数表中的哪个条目上存在分歧。

这里出了什么问题?我们违反了 ODR。所以现在两个翻译单元在虚函数表中应该是什么位置上存在分歧。因此,如果我们在一个翻译单元中创建一个类并从另一个翻译单元调用其中的虚函数,我们可能会调用错误的虚函数。哎呀哎呀!

请不要刻意做相关标准规定不允许也不行的事情。你永远无法想到它可能出错的所有可能方式。这种推理在我几十年的编程生涯中造成了许多灾难,我真的希望人们不要刻意制造潜在的灾难。

  • "There's no fundamental difference between virtual and regular member functions": That's *so* not true.
  • You know as well as I do that the relevant fundamental difference is in an additional indirection for virtual functions, necessitating a run time mechanism with type information to resolve calls. Purely conceptually they can not generally be resolved at compile time. Non-virtual functions, by contrast, *can be* resolved at compile time. This is important for performance; it is the reason for Bjarne's conscious design decision to have them at all and not simply make all member functions virtual. Consequently all implementations exploit that and *do* resolve calls at compile time.
  • (ctd.) For this, they use the ancient C mechanisms: Member functions (not telling you anything new here) are from the link perspective freestanding functions which are resolved by symbol name. This fundamental difference is relevant here because for the standard C function call resolution mechanism the order of declaration is irrelevant. It *is,* typically, relevant for the run time mechanism of virtual calls though. Since you know all this I'm genuinely unsure what your motivation was to make that assertion or ask that question ;-).

以上是在C++类成员函数上使用#ifdef保护是否安全?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>