C# 9 knows to deduce the type of RHS from LHS, just call New
with the correct parameters and have your object. No need to repeat lengthy types, and the type is always correct
List<int> ints = New(capacity: 10_000);
Can the same DRY new
be done in c ?
I have something similar using "safe" new and delete that take the target as argument and deduce type etc using templates, but it is a bit less readable than just my_var = new(arg1, arg2, arg3)
Please note this is a question about how to do a = new(1,2,3)
, like C#, and not about now to create new objects in c which make look something like list<vector<toupel<int,float,char>>>*a = new list<vector<toupel<int,float,char>>>()
' sometimes wrapper with shared_ptr
and co.
CodePudding user response:
First of all you should seldom use explicit memory management in modern C . Use smart pointers like std::unique_ptr
with ownership semantics.
As for your problem, you can do it in roughly the same way, but kind of opposite instead: Auto-deduction of the type in the declaration, and specify the type in the new
expression:
auto* my_pointer = new MyClass<template_type>(arguments, to, constructor);
Or using unique pointers:
auto my_unique_pointer = std::make_unique<MyClass<template_type>>(arguments, to, constructor);
As for the subject of containers, you should almost never make pointers to containers. If you need a container, just create an instance of it:
std::vector<int> v1(10000); // Create a vector of 10000 integers
CodePudding user response:
Here is my safe_new
and safe_delete
variant:
Which works but is not as aesthetes as I like
template<typename T>
void safe_delete(T **p) {
if (*p == nullptr)
return; // do not delete twice
delete *p;
*p = nullptr;
}
// call new but make sure to delete previous object
template<typename T, typename... Types>
void safe_new(T **p, Types... args) {
if (*p != nullptr)
delete p; // do not delete twice
*p = new std::remove_pointer<std::remove_pointer<decltype(p)>::type>::type(args...);
}
Usage:
PagedHashMap<tuple<int, int, float>, Edge, boost::hash<tuple<int, int, float>> > *edge_map;
Call new with parameters but without explicit type:
safe_new(&edge_map, 2000000));
Please note this is not an exact answer - A better answer would be something like:
edge_map = new(20000);
CodePudding user response:
First, the meanings of new
in C and C# sharp are different. With C#, you would need to use new
whenever you try to invoke a constructor. However with C , you should only ever use new
when you are trying to create an object whose lifetime is not limited by the scope it was created in. And even if you do need to manage object lifetime manually, you should consider using smart pointer instead.
So this code in C#:
List<int> ints = new(capacity: 100);
ints = new(capacity: 1000);
To rewrite it in C , you really should not use new
. Instead it should be written like:
list<int> ints(100);
ints = list<int>(1000);
Now back to your problem, you were wondering at the second line, if you can reassign ints
without typing out its type. While the type in this example is quite short, it could be quite verbose if the type was actually pretty long.
One way you can make it shorter is by creating a helper function that can create any object:
template<typename ReturnType, typename ... Args>
ReturnType create(Args&& ... args)
{
return ReturnType(std::forward<Args>(args)...);
}
What it does is simply forward all the arguments to the ReturnType
's constructor. You can then use it like:
SOME_REALLY_REALLY_LONG_TYPE_NAME foo(100);
foo = create<decltype(foo)>(1000);
However, the decltype(foo)
still feels verbose and redundant. To further shorten it, you can create another function that takes in foo
as a reference, then do the create
inside that function:
template<typename Original, typename ... Args>
void reassign(Original& original, Args&& ... args)
{
original = create<Original>(std::forward<Args>(args)...);
}
Now your code would become:
SOME_REALLY_REALLY_LONG_TYPE_NAME foo(100);
reassign(foo, 1000);
CodePudding user response:
Here's how you could write New
in C (warning, lightly tested)
template <typename ... Args>
struct NewHelper : public std::tuple<Args...>
{
using std::tuple<Args...>::tuple;
template <typename R>
operator R*()
{
return new_impl<R>(std::make_index_sequence<sizeof ... (Args)>{});
}
template <typename R, std::size_t... I>
R* new_impl(std::index_sequence<I...>)
{
return new R(std::get<I>(static_cast<std::tuple<Args...>&>(*this))...);
}
};
template <typename ... Args>
auto New(Args&&... args)
{
return NewHelper<Args&&...>(std::forward<Args>(args)...);
}
Usage:
int* x;
x = New(42);
std::tuple<int&, std::string>* y;
y = New(*x, "Hello dangling reference");
Note, I said that you could write it, not that you should write it. This is highly counter-intuitive, anti-idiomatic C that should never be let near production code. It's fun to experiment with dangerous things, but fun is fun and work is work.