简体   繁体   中英

Can I get away with this C++ downcasting fib?

I have a C library that has types like this:

typedef struct {
  // ...
} mytype;

mytype *mytype_new() {
  mytype *t = malloc(sizeof(*t));
  // [Initialize t]
  return t;
}

void mytype_dosomething(mytype *t, int arg);

I want to provide C++ wrappers to provide a better syntax. However, I want to avoid the complication of having a separately-allocated wrapper object. I have a relatively complicated graph of objects whose memory-management is already more complicated than I would like (objects are refcounted in such a way that all reachable objects are kept alive). Also the C library will be calling back into C++ with pointers to this object and the cost of a new wrapper object to be constructed for each C->C++ callback (since C doesn't know about the wrappers) is unacceptable to me.

My general scheme is to do:

class MyType : public mytype {
 public:
   static MyType* New() { return (MyType*)mytype_new(); }
   void DoSomething(int arg) { mytype_dosomething(this, arg); }
};

This will give C++ programmers nicer syntax:

// C Usage:
mytype *t = mytype_new();
mytype_dosomething(t, arg);

// C++ Usage:
MyType *t = MyType::New();
t->DoSomething(arg);

The fib is that I'm downcasting a mytype* (which was allocated with malloc() ) to a MyType* , which is a lie. But if MyType has no members and no virtual functions, it seems like I should be able to depend on sizeof(mytype) == sizeof(MyType) , and besides MyType has no actual data to which the compiler could generate any kind of reference.

So even though this probably violates the C++ standard, I'm tempted to think that I can get away with this, even across a wide array of compilers and platforms.

My questions are:

  1. Is it possible that, by some streak of luck, this does not actually violate the C++ standard?
  2. Can anyone think of any kind of real-world, practical problem I could run into by using a scheme like this?

EDIT: @James McNellis asks a good question of why I can't define MyType as:

class MyType {
 public:
  MyType() { mytype_init(this); }
 private:
  mytype t;
};

The reason is that I have C callbacks that will call back into C++ with a mytype* , and I want to be able convert this directly into a MyType* without having to copy.

You're downcasting a mytype* to a MyType* , which is legal C++. But here it's problematic since the mytype* pointer doesn't actually point to a MyType . It actually points to a mytype . Thus, if you downcast it do a MyType and attempt to access its members, it'll almost certainly not work. Even if there are no data members or virtual functions, you might in the future, and it's still a huge code smell.

Even if it doesn't violate the C++ standard (which I think it does), I would still be a bit suspicious about the code. Typically if you're wrapping a C library the "modern C++ way" is through the RAII idiom :

class MyType
{
public:
   // Constructor
    MyType() : myType(::mytype_new()) {}
   // Destructor
   ~MyType() { ::my_type_delete(); /* or something similar to this */ }


   mytype* GetRawMyType() { return myType; }
   const mytype* GetConstRawMyType() const { return myType; }

   void DoSomething(int arg) { ::mytype_dosomething(myType, int arg);  }

private:
    // MyType is not copyable.
    MyType(const MyType&);
    MyType& operator=(const MyType&);

    mytype* myType;
};

// Usage example:
{
    MyType t; // constructor called here
    t.DoSomething(123);   
} // destructor called when scope ends

我认为拥有MyTypemytype*数据成员并在MyType的构造函数中初始化它而不是使用New()方法(顺便说一句,如果你想要它必须是静态的New()会更安全和优雅拥有它)。

Is it possible that, by some streak of luck, this does not actually violate the C++ standard?

I'm not advocating this style, but as MyType and mytype are both PODs, I believe the cast does not violate the Standard. I believe MyType and mytype are layout-compatible (2003 version, Section 9.2, clause 14: "Two POD-struct ... types are layout-compatible if they have the same number of nonstatic data members, and corresponding nonstatic data members (in order) have layout-compatible types (3.9)."), and as such can be cast around without trouble.

EDIT : I had to test things, and it turns out I'm wrong. This is not Standard, as the base class makes MyType non-POD. The following doesn't compile:

#include <cstdio>

namespace {
    extern "C" struct Foo {
        int i;
    };
    extern "C" int do_foo(Foo* f)
    {
        return 5 + f->i;
    }

    struct Bar : Foo {
        int foo_it_up()
        {
            return do_foo(this);
        }
    };
}

int main()
{
    Bar f = { 5 };
    std::printf("%d\n", f.foo_it_up());
}

Visual C++ gives the error message that "Types with a base are not aggregate." Since "Types with a base are not aggregate," then the passage I quoted simply doesn't apply.

I believe that you're still safe in that most compilers will make MyType layout-compatible with with mytype . The cast will "work," but it's not Standard.

It does violate the c++ standard, however it should work on most (all that I know) compilers .
You're relying on a specific implementation detail here (that the compiler doesn't care what the actual object is, just what is the type you gave it), but I don't think any compiler has a different implementation detail. be sure to check it on every compiler you use, it might catch you unprepared.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM