call to new SomeClass() in func parameter

Started by
6 comments, last by Khatharr 8 years, 2 months ago

Hi Guys,

A quick question. Say we have a class such as the following...


class MyClass{

 std::vector<Entity*> ents;

void addEnt(Entity *ent){

 ents.push_back(ent);

}
~MyClass(){
 for(auto it=ents.begin();it!=ents.end();it++)
 delete *it;
}
};

Now, somewhere else in the program, I would like to make the following call.


void init(){

 addEnt(new Entity());

}


When I do the above, everything works just file. However, say I would like a client (someone other than myself) to create an Entity, but with out the call to "new". For example, I would like to init like so.


void init(){
 Entity e();
 addEnt(&e);

}


Now, this does not work as e() goes out of scope, and the pointer in std::vector<Entity*> is not pointer anywhere valid.

My question is, how does one have a client use a class without calling "new".

Thanks,

Mike

Advertisement

My question is, how does one have a client use a class without calling "new".

You just don't use new; you put the object on the stack instead:

void f() {

  MyClass a;
}
But that's not actually your problem. Your problem is that your MyClass API is not designed well; it makes certain assumptions about the lifetime of the instances passed to its interface that are trivially easy to break, as you noted. addEnt() assumes that the entity given to it will outlive the MyClass instance invoking addEnt.
You could just live with this limitation, hopefully by documenting it very clearly. This is less than ideal because it's still easy to misuse your API, and good APIs should strive to be difficult to misuse. Instead, consider re-evaluating your design.
MyClass could reject ownership of the Entity objects; this means it would assume the responsibility for cleaning them up is handled outside MyClass (and you remove the code in the destructor that deletes the pointers in the entity list). This helps a little, but does still leave you in a situation where the entity can outlive the MyClass and thus create a crash bug when you eventually dereference the now-bogus pointer.
Instead, MyClass should create the entity itself, and retain the ownership of it. It can return a reference to the newly-added entity for client code to use; client code should know that it does not assume ownership of the entity because the MyClass instead has it. In that work, addEnt would look like this:

Entity* MyClass::addEnt() {
  Entity * result = new Entity();
  ents.push_back(result);
  return result;
}
If you need to be able to pass parameters to the entity constructor, you can either reflect those parameters through into addEnt() so they are passed there, or you could make addEnt copy the entity given to it:

Entity* MyClass::addEnt(const Entity & prototype) {
   Entity * result = new Entity(prototype); 
   ents.push_back(result);
   return result;
}

In C++, you could also use forwarding and template parameter packs to invoke the desired constructor of Entity through the addEnt call, and you should also consider using unique_ptr<T> instead of bare pointers within your class. Alternatively, if you want to share ownership of the entity between MyClass and the client code, you can use shared_ptr<T> for the entities.

Thanks again for the greatly detailed (and invaluble) response Josh.

I did think of using your method, for example Entity* MyClass::addEnt(), but then I considered the following problem.


class MyClass{
std::vector<Ents*> ents;

~MyClass(){
 for(auto it=ents.begin();it!=ents.end();it++)
 delete *it;
}

void addApple();
void addPear();
void addCucumber();
void add...
void addZuchini();


};

This is not my problem, just a hypothetical one. .

How does one avoid bloating the functions? For example we have 200 different types of entities (just a hypothetical) is there a way to avoid 200 void addSomethings()?

Thanks,

Mike

Assuming all those types are Entity subclasses, one option is:


template<typename ClassType, typename... ArgumentTypes>
void addEntity(ArgumentTypes &&... arguments) {
  Entity * result = new ClassType(std::forward<ArgumentTypes>(arguments)...);
  ents.push_back(result);
  return result;
}

Call this as:


MyClass m;
m.addEntity<FooEntity>(parameters, for, foo);
m.addEntity<BarEntity>(parameters, for, bar);

This requires C++11 (at which point you should also move to std::unique_ptr<T>, but I left it out for brevity).

Thanks a bunch Josh. I will try that out.

Mike

One more question that I have been wondering about.

Are there any practical differences between this...


void func(){
 ents.push_back(new Ent());
}

and this


void func(){
 Ent *ent=new Ent();
 ents.push_back(new Ent());

}

if in the destructor we delete all ents?

Thanks,

Mike

In that simple case, they're basically the same, assuming that you typo'd new Ent() in the push_back() call and meant to write push_back(ent) instead.

However, if you use exceptions, the difference becomes more important, especially if you have multiple parameters for a function call. Since parameter evaluation order is implementation-dependent in C++, the following is a problem:

foo.Operate(new Bar(), new Baz());
If the constructor of Bar or Baz throws, you wind up with a potential leak of the other object. This is (one reason) why smart pointers are useful. If you have operations that might throw, the safe thing to do is always store them in intermediate wrappers until needed:

std::unique_ptr<Bar> bar(new Bar());
std::unique_ptr<Baz> baz(new Baz());
foo.Operate(bar, baz);

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique

It's worth mentioning that you don't need to manually dispose of smart pointers. You may have realized that, but I've seen people do all kinds of strange things with them.

Also, you can now:


for(auto it=ents.begin();it!=ents.end();it++) // not have to deal with this

and


for(auto&& ent : ents) // get to do this instead

Note that 'ent' in this case is not an iterator, but refers directly to the object.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

This topic is closed to new replies.

Advertisement