Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong object type with inheritance unless virtual function is present #645

Closed
virtuald opened this issue Feb 6, 2017 · 4 comments
Closed

Comments

@virtuald
Copy link
Contributor

virtuald commented Feb 6, 2017

I'm on Fedora 24 with GCC 6.3.1, Python 3.5, haven't tested on other platforms. Here's the test code:

#include <pybind11/pybind11.h>
#include <pybind11/functional.h>
namespace py = pybind11;


class A {
public:
    const char * whoami() { return "A"; }
};

class B : public A {
public:
    const char * whoami() { return "B"; }
    const char * b_specific() { return "I am B"; }
};

A* create_b() {
    return new B();
}

void call_with_b(std::function<void(const A&)> fn) {
    B b;
    fn(b);
}


PYBIND11_PLUGIN(python_example) {
    py::module m("python_example");
    
    m.def("create_b", &create_b);
    m.def("call_with_b", &call_with_b);
    
    py::class_<A> a(m, "A");
    a.def("whoami", &A::whoami);
    
    py::class_<B, A> b(m, "B");
    b.def("whoami", &B::whoami);
    b.def("b_specific", &B::whoami);
    
    return m.ptr();
}

I execute the following and get:

$ python3 -c 'import python_example; print(type(python_example.create_b())); python_example.call_with_b(lambda b: print(type(b)))'
<class 'python_example.A'>
<class 'python_example.A'>

As you can imagine, this isn't quite what I expected. I played around a bit, and I discovered that when I added a virtual function to A, then this magically started working as I expected:

$ python3 -c 'import python_example; print(type(python_example.create_b())); python_example.call_with_b(lambda b: print(type(b)))'
<class 'python_example.B'>
<class 'python_example.B'>

Is there something I'm missing here, or is this intended behavior?

@virtuald virtuald changed the title Wrong object type with multiple inheritance unless virtual function is present Wrong object type with inheritance unless virtual function is present Feb 6, 2017
@jagerman
Copy link
Member

jagerman commented Feb 6, 2017

See http://stackoverflow.com/questions/4227328/faq-why-does-dynamic-cast-only-work-if-a-class-has-at-least-1-virtual-method.

Basically, you have to have at least one virtual method in order to have a proper polymorphic class. In practice, that's almost always the case anyway because you almost always want to have a virtual destructor (virtual ~A() = default;) in your base class, and especially when using pybind11: we're holding the object in an A pointer, and destroying through that pointer which is Undefined Behaviour if you don't have a virtual destructor.

@virtuald
Copy link
Contributor Author

virtuald commented Feb 6, 2017

Ok, if that's the case, then this would be a great thing to call out in the documentation in the inheritance section.

@dean0x7d
Copy link
Member

dean0x7d commented Feb 7, 2017

Ok, if that's the case, then this would be a great thing to call out in the documentation in the inheritance section.

This isn't really specific to pybind11. It applies to anything in C++: If you have an owned base pointer to a derived class, the base must have a virtual destructor. Otherwise, the behavior is undefined in C++. Pybind11 docs are pretty extensive as it is (it takes quite a while to read everything), so I'm not sure including C++ 101 topics is entirely useful.

@virtuald
Copy link
Contributor Author

virtuald commented Feb 7, 2017

If the users of pybind11 were exclusively people who use C++ extensively, I might agree. However, I suspect there are more than a few users who don't use C++ on a daily basis but just need to wrap something so they can use it in python. Having a note for those users would be a good thing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants