Storing objects in a list



  • On 20/05/2013 at 12:32, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   R14Stdio 
    Platform:   Windows  ;   
    Language(s) :     C++  ;

    ---------
    I want to store objects in a list, and while this is as basic as it can be when programming, I do not know what the best way is for C4D plugins, because I eventually might want to compile it for Mac too.
    I guess the Basecontainer is a suitable class for this(?).

      BaseContainer* myContainer = new BaseContainer();
      myContainer->SetData(1, "Foo");
      myContainer->SetData(2, "Bar");
      
      MySpecialClass* mySpecialClass = new MySpecialClass();
      myContainer->SetData(3, mySpecialClass);
      
      //Getting the strings is no problem
      String s1 = myContainer->GetString(1);
      //but getting out mySpecialClass from the list again - I am banging my head against the wall..
    

    All I want is to be able to store a number of instances of mySpecialClass in a list. Maybe I could just store the pointers? In Pascal I did this all the time, but here..



  • On 20/05/2013 at 13:36, xxxxxxxx wrote:

    You can use any kind of container that is available cross-platform, and I haven't heard of any
    kind of container that is preferred on windows or mac. You can use the standart-library or the
    Cinema 4D classes. The only thing with the std lib is, that is uses Exception, in contrast to the
    C4D API.

    Check out std::list, std::vector or from the C4D API GeDynamicArray. All of these are templates (or

    generics, as you might know from Java or C#).

    Best,
    -Nik



  • On 20/05/2013 at 14:03, xxxxxxxx wrote:

    Hi Niklas, yes I am absolutely familiar with Generics, I use it all the time in C#.
    But man - it is different from C# though Smile
    So thank you, the std::list is precisely what I need!! Great!

    While we are at it, cannot object addresses be stored as integers?
    Like this:

    int myobjectAddress = &someObject;
    

    And then just store myobjectAddress anywhere where I can store integers, in arrays, lists etc. I am used to this from Delphi's Pascal.



  • On 20/05/2013 at 14:13, xxxxxxxx wrote:

    Hi Ingvar,

    yes, technically an object-address is just an integer. You can convert an address to an integer, but
    you'd be better off just using pointers instead (which is just an integer treated as a memory address
    to an object by the compiler).

    #include <c4d.h>
    #include <list>
      
    // ...
      
    std::list<BaseObject*> objects;
      
    // Store the top-level objects in a list.
    BaseObject* op = doc->GetFirstObject();
    while (op) {
        objects.push_back(op);
        op = op->GetNext();
    }
      
    // Iterate over them again.
    std::list<BaseObject*>::iterator it = objects.begin();
    for (; it != objects.end(); it++) {
        // You can implicitly access all attributes of BaseObject via ``it``.
        String name = it->GetName();
        // ...
    }
    

    Best,
    -Niklas



  • On 20/05/2013 at 15:01, xxxxxxxx wrote:

    Thanks for the code!
    I got one example from MSDN working, where the Generic object is not a pointer.
    In your example, when using a pointer, like BaseObject*, I get an error:

    Error	3	error C2039: **'GetName' : is not a member of 'std::_List_iterator <_Mylist>'**
    

    Can you reproduce this?



  • On 20/05/2013 at 16:47, xxxxxxxx wrote:

    Fair warning.
    The GeArray types are the only officially supported lists(arrays). And they are cross platform too AFAIK.
    Maxon does not like it when we use things like the Standard Library. If you use it, you are on your own. And they will not help you with any code problems if you are using it.

    With that disclaimer out of the way.
    Try it like this and see if it works better for you:

    //This example uses the Standard Library instead of the C4D API to store objects into a list array  
    //WARNING!!: Maxon does not like it when you do this. And will probably ignore requests for help if you use it!  
      
    #include <c4d.h>  
    #include <list>  
    using namespace std;  
      
      
      BaseDocument *doc = GetActiveDocument();  
      
      list<BaseObject*> objects;                 //Create an empty list array  
      
      BaseObject *obj = doc->GetFirstObject();   //Start the iteration from the first object in the OM  
      if(!obj) return FALSE;  
       
      while (obj)  
      {  
          objects.push_back(obj);                //Store the objects in the list array  
          obj = obj->GetNext();  
      }  
        
      list<BaseObject*>::iterator it;  
      for (it=objects.begin(); it != objects.end(); it++)  
      {   
          BaseObject *listObj = *it;  
          String name = listObj->GetName();  
          GePrint(name);  
      
          //etc...  
      }
    

    -ScottA



  • On 20/05/2013 at 17:19, xxxxxxxx wrote:

    Thanks! In the man time I got it working. I never wanted to store a BaseObject*, I want to store my own class instances.
    But initially I asked for a way to use Maxon approved lists. Because I suspected that cross platform code has to be catered for. And I believe the BaseContainer is such. So if you read my original post in this thread, I come to a point where I can store objects, but not read them.
    If I could use the BaseContainer or any other "container" I would be interested.



  • On 20/05/2013 at 17:42, xxxxxxxx wrote:

    I've never tried to store a custom class in a list before. So I can't be of much help with that.

    The GeData class is another common way to store things in C4D. And the docs mention that they can be used for storing custom class types.
    But I must confess I've never even attempted it. So I don't know if that class would be any help.

    -ScottA



  • On 20/05/2013 at 18:13, xxxxxxxx wrote:

    Ok, I found a way that works.
    To what extent it is patent C++, and furthermore C4D safe, I have no idea.
    But as soon as I understood how you dereference pointers to objects in C++ (I have used it a lot in Pascal), I got something that so far seems to work ok:

     myContainer->SetLong(4, (LONG)&mySpecialClass);
     // Then to get it out again:
     MySpecialClass* mySpecialClass2 = (MySpecialClass* )myContainer->GetLong(4);
     String test = mySpecialClass2->DoSomethingMeaningful();
    

    Hope this will be ok with Mr. Maxon ;)



  • On 20/05/2013 at 23:16, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    The GeArray types are the only officially supported lists(arrays). And they are cross platform too AFAIK.

    They are not the only supported types. Actually I rather recommend against using GeArray types, as they are rather slow, seen from nowadays standards. Use c4d_misc::BaseArray<> instead, if you need arrays. They are blazing fast, support sorting and iterators, and have almost no overhead.
    Or, if you want it the old-fashioned way, how about a simple AtomArray?

    Originally posted by xxxxxxxx

    So thank you, the std::list is precisely what I need!! Great!

    Objection, your honor.

    Originally posted by xxxxxxxx

    Maxon does not like it when we use things like the Standard Library. If you use it, you are on your own.

    Exactly :-)



  • On 20/05/2013 at 23:42, xxxxxxxx wrote:

    My fault, sorry. You first have to dereference the iterator. 🙂

    (*it)->GetName()
    

    Ingvar, using a container for this is really not a good idea I think. 1. the BaseContainer is intended
    as a mapping type, 2. you need to do a cast every-time you want to access. Not that this will have
    any cause on the performance on the program, but results in clunky and large code.

    You can use the GeDynamicArray class from the Cinema SDK as well (as I have already mentioned
    in my first answer).

    #include <ge_dynamicarray.h>
      
    GeDynamicArray<BaseObject*> objects;
      
    // Store the top-level objects in a list.
    BaseObject* op = doc->GetFirstObject();
    while (op) {
        objects.Push(op);
        op = op->GetNext();
    }
      
    // Iterate over them again.
    LONG count = objects.GetCount();
    for (LONG i=0; i < count; i++) {
        BaseObject* obj = objects[i];
        // ...
    }
    

    PS: Code is untested, intended to give you a small overview over the usage only.

    Best,
    -Nik



  • On 21/05/2013 at 00:16, xxxxxxxx wrote:

    ...OR you can use a shiny BaseArray instead of the dusty GeDynamicArray :-P



  • On 21/05/2013 at 00:21, xxxxxxxx wrote:

    Is it much faster than the GeDynamicArray? I must admit that I have not yet taken a look
    into the c4d_misc namespace. From the name, I always thought it would be a fixed size array. 😊



  • On 21/05/2013 at 01:41, xxxxxxxx wrote:

    Never assume, always look :-) The fact that BaseArray has methods like Push(), Insert() and Resize() tells you it's dynamic.

    And about the speed: The BaseArray is not just faster, it's ridiculously fast. Really.

    Here's some code I just wrote to benchmark it (as I didn't have any concrete numbers) :

    void MyBench(LONG cnt)   
    {   
         GeDynamicArray<Real> dynamicArray;   
         c4d_misc::BaseArray<Real> baseArray;   
         LONG i;   
         LONG timer = 0;   
      
         Real x = 3.14165;   
      
         GePrint("Array Benchmark (" + LongToString(cnt) + ")");   
      
         // Push()   
         GePrint("GeDyamicArray::Push()...");   
         timer = GeGetTimer();   
         for (i = 0; i < cnt; i++)   
         {   
              dynamicArray.Push(x);   
         }   
         GePrint("..." + LongToString(GeGetTimer() - timer) + " msec.");   
      
         GePrint("BaseArray::Push()...");   
         timer = GeGetTimer();   
         for (i = 0; i < cnt; i++)   
         {   
              baseArray.Append(x);   
         }   
         GePrint("..." + LongToString(GeGetTimer() - timer) + " msec.");   
      
         // Reading   
         GePrint("GeDynamicArray[]...");   
         timer = GeGetTimer();   
         for (i = 0; i < cnt; i++)   
         {   
              x = dynamicArray[i];   
         }   
         GePrint("..." + LongToString(GeGetTimer() - timer) + " msec.");   
      
         GePrint("BaseArray[]...");   
         timer = GeGetTimer();   
         for (i = 0; i < cnt; i++)   
         {   
              x = baseArray[i];   
         }   
         GePrint("..." + LongToString(GeGetTimer() - timer) + " msec.");   
      
         // Pop()   
         GePrint("GeDynamicArray::Pop()...");   
         timer = GeGetTimer();   
         for (i = 0; i < cnt; i++)   
         {   
              x = dynamicArray.Pop();   
         }   
         GePrint("..." + LongToString(GeGetTimer() - timer) + " msec.");   
      
         GePrint("BaseArray::Pop()...");   
         timer = GeGetTimer();   
         for (i = 0; i < cnt; i++)   
         {   
              x = baseArray.Pop();   
         }   
         GePrint("..." + LongToString(GeGetTimer() - timer) + " msec.");   
      
         GePrint("Array Benchmark finished.");   
    }   
    

    I built it using the latest Intel Compiler (version 13) as a 64 Bit Release build and ran it with different cnt values:

    MyBench(10000);   
    MyBench(100000);   
    MyBench(1000000);   
    

    And here are the results (on a 27" iMac with 3.4Ghz i7 and 8GB RAM) :

      
    10000 elements   
                       Push           []           Pop   
    GeDynamicArray     1 msec        0 msec       5 msec   
    BaseArray          0 msec        0 msec       0 msec   
      
    100000 elements   
                       Push           []           Pop   
    GeDynamicArray     602 msec       0 msec       602 msec   
    BaseArray          0 msec        0 msec       0 msec   
      
    1000000 elements   
                       Push           []           Pop   
    GeDynamicArray     272085 msec    0 msec       271149 msec   
    BaseArray          9 msec        0 msec       0 msec   
    

    By the way, GeAutoDynamicArray and GeSafeDynamicArray are even slower.



  • On 21/05/2013 at 01:58, xxxxxxxx wrote:

    Thanks Jack, this is a very useful resource! Those differences in speed are tremendous! You convinced
    me rather using the BaseArray instead.. ;-)

    Best,
    -Nik



  • On 21/05/2013 at 02:58, xxxxxxxx wrote:

    Uhm, how do I copy a BaseArray to another BaseArray? Copy&Assign is disallowed for the BaseArray
    class. I get compiler errors when doing

    array1 = array2
    

    "" error C2248: 'c4d_misc::BaseArray<T>::operator =' : cannot access private member declared in class 'c4d_misc::BaseArray<T>' ""

    Thanks,
    -Niklas



  • On 21/05/2013 at 03:02, xxxxxxxx wrote:

    Nevermin, just found the "CopyFrom" method. 😂



  • On 21/05/2013 at 03:30, xxxxxxxx wrote:

    Wow - what an interesting thread!
    I thank you all for all new knowledge. I have a few comments though. My experience in general, is that while you can make speed tests, they are not always reliable. You have something called a compiler which lives its own superior life and is the ultimate decision maker. Certain ways of doing things might be fast in one situation, slow in another.
    Anyhow - for the plugins I write, my speed concern is purely to speed up me. To get things done. My  current plugins execute more than fast enough, regardless of list implementation.
    But I like what I see about the BaseArray, so I will go for that one.



  • On 21/05/2013 at 05:07, xxxxxxxx wrote:

    Of course, the compiler is responsible for the final performance. Anyway, if one array type takes 272085 msec to accomplish a certain task, and another type takes 9 msec, it's pretty obvious that the first type will always be the slower one.



  • On 21/05/2013 at 07:34, xxxxxxxx wrote:

    You can't blame me too much for not recommending the the BaseArray Frank.
    Because the only rolled out in the in R14. And like most people. I'm still using older versions. 😉

    I've been wondering what's the benefit for putting a class inside of a container?
    The class is always there. And you can create an instance of it whenever you want. So I don't understand what benefit comes from stuffing it into a B.C.?
    Where (in what case) would you need to use such a thing?

    -ScottA


Log in to reply