5

I have a header file foo.h like this (unrelated stuff omitted):

#pragma once
#include <memory>

class Bar;


struct Foo
{
  std::shared_ptr<Bar> getBar();

  std::shared_ptr<const Bar> getBar() const
  {
    return const_cast<Foo*>(this)->getBar();
  }
};

The non-const overload of getBar() is implemented in a .cpp file, which also sees the full definition of Bar.

When foo.h is included from another file (which does not see the definition of Bar), VS 2010 is giving me a warning like this:

warning C4150: deletion of pointer to incomplete type 'Bar'; no destructor called

on the const overload of getBar() (or actually on something deep in the standard library instantiated from that overload).

My question is whether that warning can safely be ignored.

The way I look at it, there are two member functions of std::shared_ptr<Bar> being called in getBar() const: the converting constructor and the destructor.

// converting constructor
template <class Y>
std::shared_ptr<const Bar>::shared_ptr(std::shared_ptr<Y> &&r)

This is used to initialise the return value of getBar() const from the return value of getBar(). This does not list any prerequisites (C++11 27.2.2.1 §20-22) which would require Y (Bar in my case) to be complete.

// destructor
std::shared_ptr<const Bar>::~shared_ptr()

27.2.2.2 §1 states that when the shared pointer being destroyed is empty, there are no side effects.

I understand why I'm getting the warning - the destructor code also has to care for the situation when delete has to be called on the stored pointer, and this code would indeed delete an incomplete type. But the way I see it, it can never be reached in my situation, so getBar() const is safe.

Am I correct, or have I overlooked a call or something which could make getBar() const actually delete an incomplete type?

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 1
    `const_cast(this)->getBar();` is **BAD** trick - you'd better call const version from non-const version. – ikh Aug 20 '14 at 12:49
  • 2
    @ikh I could never grasp this argument. In my case, if the non-const version modifies `*this`, you're screwed. In your case, if the const version returns a pointer to something *actually declared* `const`, you're screwed. Both have a failing point, yours just needs one extra cast. – Angew is no longer proud of SO Aug 20 '14 at 12:54
  • 1
    In const-calls-non-const, it's up to the user of the class to make sure the const version isn't called on a const object. In non-const-calls-const, it's up to the creator of the class to make sure the logic is valid. Don't make your class a bomb for a user who didn't inspect your logic. – Neil Kirk Aug 20 '14 at 13:52
  • @NeilKirk I don't think it burdens the user, it just requires the implementor of the class not to modify `*this` in the non-const overload. – Angew is no longer proud of SO Aug 20 '14 at 13:55
  • If it doesn't modify `*this` then the function should be made const. Oops now you have two const functions! – Neil Kirk Aug 20 '14 at 13:57
  • @NeilKirk Well, `std::vector::at()` does not modify the vector object itself either, and it still has a non-const overload. Same situation here. – Angew is no longer proud of SO Aug 20 '14 at 13:59
  • http://stackoverflow.com/questions/25406818/is-the-following-use-of-const-cast-undefined-behavior – Neil Kirk Aug 20 '14 at 14:04

2 Answers2

8

I can find no rationale for the warning. Nor can I replicate the warning with clang/libc++.

In general, given a shared_ptr<Bar>, without seeing the construction of shared_ptr<Bar> that takes a Bar*, and optionally a deleter, there is no way to know for sure if ~Bar() is ever called. There is no way to know what deleter was stored in the shared_ptr<Bar>, and given some unknown deleter d that is stored in the shared_ptr<Bar>, along side of the Bar* (say p), there is no requirement that d(p) call ~Bar().

For example, your Bar may have no accessible destructor:

class Bar
{
    ~Bar();
};

And your Foo::getBar() could be implemented like this:

std::shared_ptr<Bar>
Foo::getBar()
{
    // purposefully leak the Bar because you can't call ~Bar()
    return std::shared_ptr<Bar>(new Bar, [](Bar*){});
}

There is no way for the compiler to know without seeing foo.cpp.

This warning looks like a compiler bug to me, or possibly a bug in the implementation of std::shared_ptr.

Can you ignore the warning? I don't know. It looks to me like you are dealing with a bug in the implementation, and so that bug could well mean that the warning is real. But assuming a fully conforming implementation, I see no requirement for Bar to be a complete type in the code you have shown.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
2

No; the warning cannot safely be ignored. Your code creates a shared_ptr object. The shared_ptr constructor is a template that creates and stores the deleter. By adding code to create a shared_ptr in your header, you wound up prematurely instantiating the template constructor.

The deleter trick used by shared_ptr allows you to declare them before defining the class, but they still need to see the complete type before you first use them. Your code has no guarantee to call Bar's destructor, ever. What's worse is that, today, it could even work, but might be a time bomb, making a very difficult to debug bug appear in your code.

The problem has nothing to do with the contents of the pointer in your code. Just the fact that you have code that creates a shared pointer, which does not see the complete definition of Bar, is enough.

The fix is easy. Just put that code in your implementation file, after the complete declaration of Bar.

Edit: The warning given by g++ fits this situation, but the code does not. For now I'm going to leave this answer up until I have time to investigate (or someone else figures out why g++ gives this warning).

H Walters
  • 2,634
  • 1
  • 11
  • 13
  • I don't think that the constructor which takes a `shared_ptr` actually creates a deleter - it shares ownership with the argument, so it must use its deleter (or lack thereof) as well. Notice that the ctor taking a raw pointer has a requirement of the pointed-to type being complete, but the ctor which takes a `shared_ptr` does not. – Angew is no longer proud of SO Aug 20 '14 at 13:53
  • It's not "a constructor which takes a shared_ptr" that I'm talking about--it's the shared_ptr's constructor itself. That's a template constructor--it works similar to a template function, in that it matches the call arguments to the template parameters, deriving the types to use based on that. Once that template matches, it will instantiate the constructor. That constructor creates the deleter, and it's that deleter that sees an incomplete type. This is precisely why g++ is issuing this warning. – H Walters Aug 20 '14 at 14:16
  • But I am only constructing the `shared_ptr` from another `shared_ptr` in the question code. Does overload (10) in your link create a deleter? – Angew is no longer proud of SO Aug 20 '14 at 14:20
  • Don't think so... at least standardly. Yeah, out of the thin interface of my phone, couldn't see exactly what you were doing. But this is the warning g++ gives for that situation, so the implementation might still be trying to instantiate the template somehow. – H Walters Aug 20 '14 at 14:23
  • My working theory is that there's code generated for the dtor of const Bar giving the warning when converting shared_ptr to shared_ptr, but I'm about to hop on a plane, so can't investigate that at the moment. This is an interesting one... I'll be checking back. – H Walters Aug 20 '14 at 14:36
  • Yes, that's what I believe as well. If I understand the template spew correctly, the warning actually comes from the dtor of `shared_ptr`. Of course, that *could* call `delete` on an incomplete type if the internal pointer was not null, but that will never happen in my case. – Angew is no longer proud of SO Aug 20 '14 at 14:39
  • Curious... can't reproduce this issue in VS 2010 Express given the above description (assuming the express compiler/STL isn't significantly different than Studio proper); I'll pick at it a few more days when I get spare moments. – H Walters Aug 21 '14 at 03:42