I have a project that tightly controls what and how can allocate memory. I have a base class for things that may be allocated on a heap, with static overloads for operator new and operator delete and their array variants. These work perfectly without any warnings at all.
There's an ultimate base class for everything that allows placement new only:
class Object
{
public:
static void* operator new(size_t, void*);
static void* operator new[](size_t, void*);
static void operator delete(void*, void*);
static void operator delete[](void*, void*);
};
The implementations are trivial and in the corresponding .cpp file. operator news return the pointer, operator deletes don't do anything.
When I compile this under VS2015, using new (ptr) DerivedFromObject() generates the following warning. Exception handling is set to /EHa.
warning C4291: 'void *Object::operator new(std::size_t,void *)': no matching operator delete found; memory will not be freed if initialization throws an exception
I've tried messing around with the signatures: adding noexcept, adding size_t to operator delete but nothing seems to work. What's the correct form of static member placement operator delete?
It seems that I need to declare operator deletes in every derived class to get rid of the warning.
Related
I have made a BaseObject class that overrides the new (and delete) operators, so I can log memory as it is created and destroyed during the lifecycle of my application, making it easy for me to spot any unreleased memory.
However, I am getting the following errors when I compile (but it runs fine):
resourcemanager.h(36): warning C4291: 'void *BaseObject::operator new(size_t,const char *,int)': no matching operator delete found; memory will not be freed if initialization throws an exception
baseobject.h(8): note: see declaration of 'BaseObject::operator new'
My BaseObject class looks like this:
class BaseObject
{
public:
void* operator new(size_t size, const char* file, int line);
void* operator new[](size_t size, const char* file, int line);
void operator delete(void* ptr);
void operator delete[](void* ptr);
};
#define MY_NEW new(__FILE__, __LINE__)
#define MY_DELETE delete(__FILE__, __LINE__)
From what I read on the internet, the problem is due to the fact that the overridden delete operators need matching signatures. So in this case, it should work if I added "const *char file, int line" to the delete operators. But then I run into another problem as I cannot call delete on any objects since it is not finding a matching delete operator (it must now take file and line as input). Using the MY_DELETE macro above does not work in this case as delete cannot use parameters like that.
Pretty much at a loss here.
EDIT:
The solution was not immediately obvious. Adding overridden delete functions with the same extra parameters as the new functions was not enough since this yielded a problem with there being "no non-placement delete operator". This in turn could be solved by adding a non-placement delete operator. So, the class looks like this now (and it is working):
class BaseObject
{
public:
void* operator new(size_t size, const char* file, int line);
void* operator new[](size_t size, const char* file, int line);
// Placement delete functions, matching overridden new functions
void operator delete(void* ptr, const char* file, int line);
void operator delete[](void* ptr, const char* file, int line);
// Non-placement delete functions
void operator delete(void* ptr);
void operator delete[](void* ptr);
};
From cppreference https://en.cppreference.com/w/cpp/language/new :
If initialization terminates by throwing an exception (e.g. from the
constructor), if new-expression allocated any storage, it calls the
appropriate deallocation function: operator delete for non-array type,
operator delete[] for array type. The deallocation function is looked
up in global scope if the new-expression used the ::new syntax,
otherwise it is looked up in the scope of T, if T is a class type. If
the failed allocation function was usual (non-placement), lookup for
the deallocation function follows the rules described in
delete-expression. For a failed placement new, all parameter types,
except the first, of the matching deallocation function must be
identical to the parameters of the placement new. The call to the
deallocation function is made the value obtained earlier from the
allocation function passed as the first argument, alignment passed as
the optional alignment argument (since C++17), and placement_params,
if any, passed as the additional placement arguments. If no
deallocation function is found, memory is not deallocated.
So the matching signature for delete in your case is:
strip away size
put void* ptr in its place
repeat the parameters passed to operator new.
This leads to:
class BaseObject
{
public:
void* operator new(size_t size, const char* file, int line);
void* operator new[](size_t size, const char* file, int line);
void operator delete(void* ptr, const char* file, int line);
void operator delete[](void* ptr, const char* file, int line);
};
Off-topic: abstract is not a keyword in C++ and this should be a compile error. To make a class abstract, define a function as pure virtual (by putting = 0 as its definition).
I have an object of type MyType that, for SSE reasons, needs to be 16-byte aligned. So, I wrote an allocator and overloaded the new operators. Methods in MyType:
inline static void* operator new(size_t size) {
awesome::my_allocator<MyType,16> alloc;
return alloc.allocate(size);
}
inline static void* operator new[](size_t size) { return operator new(size); }
inline static void operator delete(void* ptr) {
awesome::my_allocator<MyType,16> alloc;
alloc.deallocate(reinterpret_cast<MyType*>(ptr),0); //last arg ignored in my impl
}
inline static void operator delete[](void* ptr) { operator delete(ptr); }
Now, for cache-locality reasons, I need to copy-construct an instance into a particular piece of 64-byte-aligned memory:
void MyType::copy_into(uint8_t* ptr) const {
new (reinterpret_cast<MyType*>(ptr)) MyType(*this);
}
GCC tells me:
error: no matching function for call to ‘MyType::operator new(sizetype, MyType*)’
ICC tells me:
error : function "MyType::operator new" cannot be called with the given argument list
1> argument types are: (unsigned __int64, MyType *)
As I understand it, the placement new operator is provided by the C++ implementation (or possibly by <new>, which I also tried #includeing?) and simply returns its argument (new makes memory available, and placement new is the programmer saying that the given memory is available).
Curiously, the error does not occur when the (ordinary!) new operators defined above are not in the class. Indeed, MyOtherType, which doesn't define them, works just fine.
Question: what's going on? How should I fix it?
Since you have defined operator new in your class, you need to use the global new to use the placement version of it.
#include <new>
...
::new (reinterpret_cast<MyType*>(ptr)) MyType(*this);
I want to force my object to be on the stack to enforce very strict semantics and address some lifetime concerns. I've read a couple articles on how to do this, and arrived at making operator new private (or deleted). This seems to work as expected when new is used directly, but make_shared compiles fine.
#include <boost/smart_ptr.hpp>
class A
{
private:
void *operator new( size_t );
void operator delete( void* );
void *operator new[]( size_t );
void operator delete[]( void* );
};
int main()
{
// A* a = new A; // Correctly produces compile error
boost::shared_ptr<A> a2 = boost::make_shared<A>();
}
Using new A directly gives me this error as expected:
error: ‘static void* A::operator new(size_t)’ is private
I am guessing that make_shared is working because it is using the placement new operator, but I couldn't find any articles that discussed how to prohibit this. The best solution I've come up with is to explicitly delete a template specialization for make_shared
namespace boost
{
template<>
shared_ptr<A> make_shared<A>() = delete;
};
This is obviously very specific to boost::make_shared though. Is this really the best way?
Placement forms of new are pretty easy to handle -- they just come with extra arguments. For example, the simple placement form is
void* operator new(std::size_t, void*);
Note that 18.6.1.3 prohibits redefining these at global scope; however there should be no problem with redefining (or deleting/making inaccessible) them for your particular type.
Unfortunately, make_shared uses scoped ::new (pv) T(std::forward<Args>(args)...). And as I mentioned, you aren't allowed to mess with the global placement new. So you can't prevent it at compile-time, and any runtime trap would be a hack (checking the this pointer to see whether it's in-bounds of the stack).
You cannot enforce that objects of a class are always on the stack by making any operators inaccessible only: Any object which can be constructed on the stack can also be embedded as a member into another object. Even though your original class might struggle against being allocated on the heap the containing class won't. I'd think this is what happens in the case of boost::make_shared(): Internally it probably allocates some record containing both its administration data plus the object actually being allocated. Alternatively, it may use allocation function from some sort of allocator which don't map to the type's operator new() but use its own operator new() overload instead.
I'm not sure if it is possible to prevent heap allocations (at least, when the object is embedded into another object) but any approach doing so would need to make the constructors inaccessible (most likely private) and use some sort of factory functions, possibly in combination with moving. On the other hand, if you can move an object you have an accessible constructor and nothing prevents the object from being moved into an object on the heap.
If you specifically want to prevent the use of std::make_shared() for a concrete type (or boost::make_shared() although I can't quote the rules for specializing the latter), you can specialize std::make_shared(): According to 17.6.4.2.1 [namespace.std] paragraph 1 a user is allowed to specialize any template (unless otherwise specified) if it involves a user-defined type. Thus, you can prevent A from being used with std::make_shared():
class A
{
public:
A();
A(int);
};
namespace std
{
template <> std::shared_ptr<A> make_shared<A>() = delete;
template <> std::shared_ptr<A> make_shared<A, int>(int&&) = delete;
}
namespace boost
{
template <> boost::shared_ptr<A> make_shared<A>() = delete;
template <> boost::shared_ptr<A> make_shared<A, int>(int&&) = delete;
}
Obviously, if you have multiple constructors in A you might need to add more specializations. ... and if your type happens to be a class template or your constructor to be a template, you'll be out of luck: You cannot partially specialize function templates.
With respect to your question about placement new (which may or may not be used by make_shared()): The placement new (and delete) signatures are these:
void* operator new(size_t, void*) noexcept;
void* operator new[](size_t, void*) noexcept;
void operator delete(void*, void*) noexcept;
void operator delete[](void*, void*) noexcept;
(see 18.6 [support.dynamic] paragraph 1). I doubt that making them inaccessible will help you anything, though.
This question already has answers here:
Difference between 'new operator' and 'operator new'?
(8 answers)
Closed 8 years ago.
Interview question: what's the difference between "new" operator and "new" function?
I answered there is no difference, that they run the same code, but interviewer kept needling me like that was the wrong answer.
Is it the wrong answer? Or was the interviewer just playing games with me?
If it's the wrong answer, what's the right answer?
I continued that the "new" operator could be overloaded if you needed a custom allocation, but then he wanted to know how to overload it. Of course I didn't have that answer, having never had the need, but I told him I could look it up in 10 minutes (which is never the right answer in an interview).
So anyhow, having done some research on "new" operator vs. "new" function and not seeing any really satisfying answers, I thought I'd ask the specific question.
The new operator and operator new are not the same thing.
The new operator calls an operator new function to allocate memory, and then, depending on the type allocated and the syntax used, initializes or calls a constructor on the allocated memory. In other words, operator new forms only a part of the operation of the new operator.
operator new is the function called to allocate memory by the new operator. There's a default implementation of operator new which can be replaced, which is not the same thing as overloading. operator new can also be implemented for a particular type to handle allocations only of objects of that type, or operator new can be overloaded and the overload can be selected by using the placement new form of the new operator.
The default implementations of operator new can be replaced by defining functions with the following signatures:
void *operator new(std::size_t size);
void *operator new(std::size_t size, const std::nothrow_t&);
void *operator new[](std::size_t size);
void *operator new[](std::size_t size, const std::nothrow_t&);
When you provide a replacement or overload for operator new you should provide corresponding operator delete functions:
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, const std::nothrow_t&) noexcept;
void operator delete[](void* ptr) noexcept;
void operator delete[](void* ptr, const std::nothrow_t&) noexcept;
To provide an overload of operator new for use with the placement form of the new operator you can add additional arguments (the nothrow versions of operator new and operator delete do this).
struct my_type {};
void *operator new(std::size_t size, const my_type&);
void operator delete(void *ptr, const my_type&);
new (my_type()) int(10); // allocate an int using the operator new that takes a my_type object
There is no 'placement delete' form of the delete operator. The overload of operator delete is provided because if an error occurs during the initialization/construction of the memory (e.g., the constructor called by the new operator after operator new has been called) the corresponding operator delete is called if it exists before re-throwing the exception. Otherwise operator delete is not called and the memory leaks when the exception is thrown.
Basically:-
Function: "operator new"
class Example
{ public:
void* operator new( size_t );
}
"new operator":
Example* eg = new Example();
I continued that the "new" operator could be overloaded if you needed
a custom allocation
You are almost right.
new is a keyword for an operator, which is used for memory allocation.
Why is this an operator ?
On need basis, it can be overloaded as a function both at global scope or class scope (but NOT namespace scope!). This wouldn't have been possible if it was a function.
The difference is in how they function. The initial allocation part is, per standard I believe, the same. That is, using syntax new vs operator new() explicitly is very much the same. The difference, is using new initializes or constructs the new object. There is also 3 different versions of ::operator new() and there is various syntaxes to utilize them as well (i.e., placement new).
There is no new function. My guess is that they wanted you to say that one of these allocated the memory (the standard uses allocator function, function new operator and new operator for it) and that the other used the first to allocate memory, then called the constructor (the standard uses new expression) and the deallocator function (aka function delete operator or delete operator) to free the memory if the constructor exited with an exception.
I know that you can use a dummy "int" parameter on operator++ and operator-- to override the postfix versions of those operators, but I vaguely recall something about a dummy parameter that you could declare on a destructor. Does anyone know anything about that, and if so, what that dummy parameter did?
This was in my old Turbo C++ tutorial books, which I read when I was a teenager (i.e. a long time ago), so I might be completely misremembering it. That was also very early C++, before it was standardized, so it's possible that it was something Turbo C++-specific.
You're possibly thinking of the placement and nothrow forms of operator delete, which have the signatures:
void operator delete(void *, void *) throw();
void operator delete(void *, const std::nothrow_t&) throw();
void operator delete[](void *, void *) throw();
void operator delete[](void *, const std::nothrow_t&) throw();
These are never called during normal operation, but would be used in the case where the constructor for an object being constructed with placement new throws an exception. Generally you don't have to define them, since the compiler already called the destructor(s) on the dead object's bases and members, and for placement new there's no memory to be freed. But can exist if you are overloading placement new and need a corresponding operator.
The second argument is not really used, and just distinguishes the signature for the ordinary:
void operator delete(void *)
These aren't special dummy arguments the way the operator++ ones are, though. They're just an instance of the general rule that call to new with extra arguments, such as:
obj = new(x,y,z) Object(a,b,c)
will generate implicit code to clean up from constructor errors that passes those same additional arguments to the operator delete, which will function (approximately) like:
void *raw = operator new(sizeof(Object), x,y,z)
try {
obj = new(raw) Object(a,b,c);
} catch(...) {
operator delete(raw,x,y,z);
throw;
}
Either you are misremembering, or you should try to forget it. Destructors don't have parameters, return types and they shouldn't throw exceptions.
I swear I've heard the same thing, but the C++ FAQ seems to say that there is no such form.
Perhaps you are thinking of placement new?
class MyClass { /* ... */ };
char * raw_mem = new char [sizeof (MyClass)];
pMyClass = new (raw_mem) MyClass;
// ...
pMyClass-->(~MyClass());
delete[] raw_mem;
You're not crazy. I have definitely seen an int parameter in a destructor before. Using HP's compiler on OpenVMS, I compiled a sample program show below. The list of symbols does include an destructor with an int parameter. I can only guess this is compiler specific.
$ create foo.cxx
class foo
{
~foo() {}
};
$ cxx foo.cxx
$ type [.CXX_REPOSITORY]cxx$demangler_db.
CX3$_ZN3FOOD1EV31GNTHJ foo::$complete$~foo()
CX3$_ZN3FOOD2EV30KQI3A foo::$subobject$~foo()
CX3$_ZN3FOOD9EV36HH9SB foo::~foo(int)
CXXL$_ZDLPV void operator delete(void *)