Wstring ? international chars ?

The Partridge Family were neither partridges nor a family. Discuss.
binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

Re: Wstring ? international chars ?

Post by binbinhfr » April 10th, 2020, 1:06 am

you are right : I was inspired by LISP because of many parenthesis ;-)
As you guessed, every statement is a function with parameters. It's "Polish Notation".
Very easy to parse.
But then, a = b is written =(a,b)
a = b +3 is written =(a,+(b,3))
if(condition, (bloc1),(bloc2))
etc...

The rest of the language is like C, with special functions to command bots and also create maps (the same langage is used for the player to play bots, and for the "map designer" to describe a map and its "scenario".

Today I struggled with a memory leak, but i discovered the power of VC++ memory diag. It's incredible what compilers can do nowadays. Finding the leak using heap state was so easy, even in a multithread context (I have 1 supplementary thread for each bot/program running in parallel of the main thread that handles graphics and inputs).

I'll post a demo when it will be playable.

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Wstring ? international chars ?

Post by albinopapa » April 10th, 2020, 4:23 am

Use them smart pointers, that should help a ton.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

Re: Wstring ? international chars ?

Post by binbinhfr » April 10th, 2020, 3:22 pm

My leak was due to my "newbiness" in C++ : I did not understand that you had to declare a desctructor as virtual if you wanted both destructors (abstract and derivated) to be called. So I was not deleting objects, as I thought I was...

I wonder : can you use smart pointer everywhere ? For example, I use a lot of containers of class pointers. (list of pointers, tree of pointers, maps of pointers, etc...)

Can I use the same smart pointer in one container AND in another container at the same time ?

And most important : I create most of these pointers using a "new". So they do not really belong to the stack (to a bloc/context) but more to the heap. I wonder if smart pointer can be useful in this case ? I mean that I can created an object with a "new" in one function of the code, and destroy the object with a "delete" in another function. So I do not want the pointer to be automatically deleted at the end of the first function...

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Wstring ? international chars ?

Post by albinopapa » April 10th, 2020, 5:47 pm

can you use smart pointer everywhere ?
Yes
Can I use the same smart pointer in one container AND in another container at the same time ?
std::shared_ptr is useful for this and is kind of what it is for...shared ownership.
I mean that I can created an object with a "new" in one function of the code, and destroy the object with a "delete" in another function
If you are allocating in one function and returning the allocated pointer, then yes you can return an std::unique_ptr or std::shared_ptr from a factory function without it being deallocated.

Std::shared_ptr is for shared ownership between objects. If you have multiple containers with a pointer to the same object then std::shared_ptr is what you want.

Std::unique_ptr is for sole ownership. This is useful for short lived objects such as those allocated at the top of a function and released at the bottom, or long life ownership because it's ownership can be transferred using move semantics. Std::unique_ptr cannot be copied, only moved.

// Usage tips
Return by value from a factory function.

Code: Select all

std::unique_ptr<Thing> ThingFactory( int param1, int param2 ){
    // Easiest way to make a unique_ptr and initialize it
    // std::make_unique is a factory actually function for std::unique_ptr
    auto thing = std::make_unique<Thing>( param1 );

    // do stuff with thing
    // use like pointer
    thing->SetValue( param2 );

    // Passing to ownership non-transferring function
    DoSomethingWithThing( thing.get() );

    // return by value
    return thing;
}

// Passing to ownership transferring function

void DoStuff( ThingMaster& master )
{
    // Add thing to things vector using vector::emplace_back to return a reference
    // to the object that was just added to the end ( available starting with C++17 )
    auto thing = ThingFactory( "John Smith", 42 );
    
    // Do stuff with thing
    // do{ stuff }while( true );

    // Then transfer ownership to 'master'
    master.TakeResponsibility( std::move( thing ) );
}
void ThingMaster::TakeResponsibility( std::unique_ptr<Thing> thing_ )
{
    things.push_back( std::move( thing_ ) );
}
std::shared_ptr is different in that it is reference counted so when all of the objects destroy their copy of the pointer, it is then that it is deallocated.

Code: Select all

// Use std::make_shared to create an std::shared_ptr for efficiency.
std::vector<std::shared_ptr<Thing>> things_vec;
std::map<std::string, std::shared_ptr<Thing>> things_map;

auto thing = std::make_shared<Thing>( "John Smith", 42 );
things_vec.push_back( thing );
things_map[ "Hero" ] = thing;

// For passing to non-transferring ownership functions, call .get().  This way the reference count is 
// not affected.  
// The reference count is an atomic operation so that part of shared_ptr is thread-safe.  
// The rest is up to you to ensure it is used correctly in multi-threaded environments
DoSomethingWithThing( thing.get() );
Using std::move( thing ) on an std::shared_ptr would remove the pointer ( setting it to nullptr ) and it's reference count is 0 while the receiving copy would get the old pointer and reference count. So, if the old shared_ptr had a ref count of 5, the new one would also have a ref count of 5 and the old pointer as well.

Copying a shared_ptr increases the ref count and makes a shallow copy of the underlying pointer.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Wstring ? international chars ?

Post by albinopapa » April 10th, 2020, 5:49 pm

Oh, they can be created using polymorphism as well.

Code: Select all

class A {};
class B : public A { };

std::unique_ptr<A> a_unique_ptr = std::make_unique<B>();
std::shared_ptr<A> a_shared_ptr = std::make_shared<B>();
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Slidy
Posts: 80
Joined: September 9th, 2017, 1:19 pm

Re: Wstring ? international chars ?

Post by Slidy » April 10th, 2020, 6:01 pm

You should watch Chili's vids on smart pointers, they explain everything in depth. It's all based on the concept of ownership.

To answer your questions briefly:
Can I use the same smart pointer in one container AND in another container at the same time ?
Kind of, it depends what kind of relationship you want to have. Which container should own the memory? In other words, which container do you want to delete the block of memory?

Here are our options:
The first one - The first container should "own" the block of memory through a unique_ptr and the second container should just have a non-owning raw pointer to the memory.
The second one - The second container should "own" the block of memory through a unique_ptr and the second container should just have a non-owning raw pointer to the memory.
The last container to be destroyed - Both containers should "own" the block of memory simultaneously through a shared_ptr. shared_ptr maintains a ref count of all the usages of the block of memory. Once the last shared_ptr goes out of scope the block of memory is deleted automatically.
I mean that I can created an object with a "new" in one function of the code, and destroy the object with a "delete" in another function. So I do not want the pointer to be automatically deleted at the end of the first function...
Ownership of the smart pointer can be transferred through move semantics. Here's an example:

Code: Select all

class Foo
{
public:
  Foo() { std::cout << "Constructed\n"; }
  ~Foo() { std::cout << "Destructed\n"; }
  void OnCreate() { std::cout << "Created\n"; }
  void OnDestroy() { std::cout << "Destroyed\n"; }
private:
  int data = 0;
};

void FunctionA()
{
  auto p = std::make_unique<Foo>();
  p->OnCreate();
  return p; // returned values are automatically moved, no need for explicit std::move()
}

void FunctionB( std::unique_ptr<Foo> p )
{
  p->OnDestroy();
  // p goes out of scope and is automatically deleted
}

int main()
{
  std::unique_ptr<Foo> p = FunctionA();
  FunctionB( std::move( p ) ); // Move ownership of p to FunctionB()
  return 0;
}
Smart pointers should really only be used in places where you want ownership semantics. If you just want a non-owning view to some object you should just use references/raw pointers.
Another example:

Code: Select all

int main()
{
  std::unique_ptr<Foo> p = FunctionA();
  FunctionB( p.get() ); // FunctionB is just taking a raw pointer, it won't take ownership & delete the pointer
  return 0;
}

binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

Re: Wstring ? international chars ?

Post by binbinhfr » April 10th, 2020, 11:49 pm

Ok thanks guys !

Everything is a little clearer now. But it's true that most of the time, i am passing pointers to functions just as raw pointers, because these functions does not delete them, but just deals with their data. Coming from C, usually I am quite organized in new/delete management. The fact is that I did not know this virtual destructor behaviour.

Now, I have prepared a serie of automated benchmarks for my game, and from times to times, I run them in loop, and come back 5 mins later to see if there is a memory leak. Not 1 byte lost ! ;-)

Anyway, these behaviours above raw pointers are very interesting. I already watched chili's videos ; on the moment, I understood things, but the fact is that I did not really integrate them as a habbit. It's not always easy to realize where you should implement what chili thaught you. I'll watch this one again.
If you are allocating in one function and returning the allocated pointer, then yes you can return an std::unique_ptr or std::shared_ptr from a factory function without it being deallocated.
no, most of the time, the function that new-creates the object is also doing other stuff and returns an return/error code.

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Wstring ? international chars ?

Post by albinopapa » April 11th, 2020, 3:45 am

Well, you said pointer allocated in one function and deallocated in another...seems like a factory function to me.

There is also some benefit of using smart pointers aside from bookkeeping. If you don't have to do the bookkeeping ( tracking new/delete ) then you can cut down on declaring and defining destructors. If your class has a unique_ptr for instance and no other resources that need managing then you don't have to write a destructor for that class and the compiler can auto generate the move constructor and move assignment operators. If your class has std::vector and no other resources to manage then you won't have to write a destructor which means the compiler can auto generate a copy/move constructor and copy/move assignment operators.

Compare these two examples:

Code: Select all

class A
{
public:
    A()=default;
    ~A(){ delete ptr; }
    A( A const& other )
    {
        // initalize 'this' members with 'other' members
        // this->members = other.members
        
        // Allocate new Object with call to Object's copy constructor
        object = new Object{ other.object };
    }
    A( A&& other )
        :
    members( other.members )
    object( other.object )
    {
        // Invalidate other's member variables
        other.members = 0;
        other.object = nullptr;
    }
    A& operator=( A const& other ){
        // First make sure we aren't trying to assign to ourselves
        if( this == std::addressof( other ) return *this;

        // Calls copy constructor, then moves temp to *this
        *this = A( other );

        return *this;
    }
    A& operator=( A&& other )
    {
        if( this == std::addressof( other ) return *this;

        delete object;
        object = other.object;
        members = other.members;
        other.members = 0;
        other.object = nullptr;

        return *this;
    }

    void Methods();

private:
    Object* object = nullptr;

    // here just to represent other member variables
    T members
};

To this code

Code: Select all

class A
{
public:
    void Methods();

private:
    // unique_ptr already sets it's underlying pointer to nullptr
    std::unique_ptr<Object> object;
    T members = 0;
};

// Or
class A
{
public:
    A() = default;
    A( std::shared_ptr<Object> object_ )
        :
    object( std::move( object_ ) )
    {}

    void Methods();

private:
    std::shared_ptr<Object> object;
};
In the unique_ptr version, class A will probably be creating it's own unique_ptr, but can be passed in through a user-defined constructor. If that is the case, you might also declare a default constructor ( A() = default; ) if you want to create one inside an array for instance.

The shared_ptr version will of course need it's shared resource passed in or else it won't be a shared resource.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply