1

I'm truly amazed by the idea behind the OOP, since I find it so much easier to think of the classes and the objects, as if they are real ones. I have a base class Tooth and derived classes for the different groups of teeth (they have their specifics). Their constructors are taking as argument the texture, which is drawn; For upper teeth it goes something like this:

class Tooth 
{ 
   public:
   Tooth(Texture &texture);
};

class Molar : public Tooth { };
class Premolar : public Tooth { };
class Frontal : public Tooth { };

main()
{
   Molar *molar[6];
   Premolar *premolar[4];
   Frontal *frontal[6];

   Texture uppertextures[8];   //only 8 textures, since the other half of the teeth are mirrored

   Tooth* upperTeeth[16] = { molar[0], molar[1], molar[2], premolar[0], premolar[1], frontal[0], frontal[1], frontal[2], frontal[3], frontal[4], frontal[5], premolar[2], premolar[3], molar[3], molar[4], molar[5] }; // < the order in which the teeth have to be added to the graphic scene;

   int x = 0     //for the position of the tooth on the screen;
   int texture = 0;    //we will need it to assign the textures in order;

   for(int i = 0; i<16; i++)
   {
       upperTeeth[16] = new !?!?!?(uppertextures[i])
       upperTeeth[16]->setPositionX(i+x) // some function for setting the position on the screen from left to right;
       
       if(i<8) texture++;     //setting the textures for the teeth on the left;
       if(i>8) texture--;    //setting the textures for the teeth on the right(the same, but in reverse order);
       
       upperTeeth[16]->setPositionY(y)
}


}

As you can see the problem is with the allocation. I want to create new[] the same way I create array of the Base class, and fill in the order of the derived classes, in which the objects appear. Of course if you have completely different solution, you can share it, but that's the most clever way I could think of.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
TheFinalCutBG
  • 43
  • 1
  • 6
  • 1
    You can start by observing that assigning anything to `upperTeeth[16]` is undefined behavior. So, you're already in crash territory, right out of the starting gate. Insofar as learning how to correctly construct objects, that's something that should be very well covered [in every good C++ textbook](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). Is there anything specific in your textbook, regarding this topic, that you are unclear, and have a question about? You're not going to learn C++ by asking questions here, but only from a good textbook. – Sam Varshavchik Jul 15 '20 at 12:07
  • 1
    Its hard to understand what you actually want to achieve. How should `upperTeeth[16] = new !?!?!?(uppertextures[i])` know which kind of object to instantiate? Whats the discerning factor? Notice that the factory pattern might be interesting to you – Sebastian Hoffmann Jul 15 '20 at 12:08
  • What to write at the place of !?!?!?!. I mean for the first 3 cycles it has to be new Molar, for the 4th and the 5th cycle - new Premolar, etc. – TheFinalCutBG Jul 15 '20 at 12:11
  • Did you try using an `if` statement? Or the ternary operator, perhaps? And what exactly are you trying to accomplish, in the shown code, by assigning completely uninitialized pointers to `upperTeeth`? That's also, technically, undefined behavior. – Sam Varshavchik Jul 15 '20 at 12:13
  • You'll need an if statement then: `if (i < 3) upperTeeth[i] = new Molar(uppertextures[i]) else if (i < 5) upperTeeth[i] = new PreMolar(uppertextures[i]) etc.` – Sebastian Hoffmann Jul 15 '20 at 12:13
  • @SamVarshavchik, yes you are right. Those objects have different size. It was a mistake. Maybe I should create some kind of list, or map? – TheFinalCutBG Jul 15 '20 at 12:14
  • 1
    No, what you should do is [get a good C++ textbook, and work your way through it](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). C++ is the most complicated and hardest general purpose programming language in use today, and it's simply not possible to explain everything fully in a short answer on stackoverflow.com. SO is really not a replacement for a textbook, but only a ***specific*** Q/A site. – Sam Varshavchik Jul 15 '20 at 12:15
  • In my current phase of learning, I am trying to avoid if statements as much as possible, but if it is the right solution, then I'll do it. – TheFinalCutBG Jul 15 '20 at 12:15
  • @TheFinalCutBG Why is that the case? if statements are the bread-and-butter of programming and one of the most fundamental tools of all – Sebastian Hoffmann Jul 15 '20 at 12:16
  • 1
    That's somewhere along the lines of trying to learn how to drive without making left turns. Technically possible, but not really advisable. – Sam Varshavchik Jul 15 '20 at 12:16
  • 1
    One has to be practical about programming, and here this means don't add stuff that don't add value. I see no value in having separate classes for separate kinds of teeth. What if a patient has some deviant tooth that cannot be categorized? – Dialecticus Jul 15 '20 at 12:16
  • @Dialecticus Little of-topic: Believe me, there is SO MUCH about different classes for different kinds of teeth. In the real world there are classified this way for a reason. And no - there is no such thing as deviant tooth. – TheFinalCutBG Jul 15 '20 at 12:22
  • @TheFinalCutBG Here's how I would solve this: `class Teeth { std::array, 16> m_teeth; public: Tooth& at(std::size_t idx); Molar& at_molar(std::size_t idx); //etc };`. The `at_molar` methods could either use relative (e.g return the **first** molar tooth) or absolute indices (e.g return the 4-th tooth as `Molar&`) but should fail (assertion or exception) regardless if the index is out-of-bounds/invalid. This way you have a way to iterate over all teeth for general properties but access specific properties for specific tooth in a safe way. – Sebastian Hoffmann Jul 15 '20 at 12:36
  • Don't be fooled by reality of teeth. You don't create classes that correspond to reality as close as possible, but classes that get the job done in satisfactory way. But on point, remove arrays `molar`, `premolar` and `frontal` from code, and add a pointer to `upperTeeth` array once you create and initialize a proper object. Also use `std::array` instead of C-style arrays that you're using now. – Dialecticus Jul 15 '20 at 12:36

2 Answers2

2

The reason a lot of people are going to tell you to "get a textbook" is because there's a lot of machinery going on in C++. But don't worry, there's plenty of learning you can do without one. Practice is probably the best way to learn.

... There's a few things to consider finding a solution to your problem.

First, there's the constructors you've set up. You've got child classes that inherit from a class that has a constructor with arguments. That's not good, it basically means you can't create your child classes. You'll just get errors if you try. So I've changed all the constructors to "default" so that it actually runs, but you should make child constructors that build the base class too, like

Molar(Texture& texture) 
    : Tooth(texture) 
{
    // ... do whatever here
}

Then there's ownership. Which class "owns" the tooth data? You've got your arrays, but they are arrays of pointers so they don't actually own the data. So you can own the data somewhere else, to make sure it doesn't get trash-collected outside of your function context. Or just make it so the arrays actually contain the teeth classes. I've done that in my solution because it's simpler. But you can see upperTeeth is still a pointer array because you likely don't want to own two copies of the objects.

Molar molar[6];
Premolar premolar[4];
Frontal frontal[12];

Texture uppertextures[8];   // Only 8 textures, since the other half of the teeth are mirrored

// The order in which the teeth have to be added to the graphic scene
Tooth *upperTeeth[16] = { &molar[0], &molar[1], &molar[2], &premolar[0], &premolar[1], &frontal[0], &frontal[1]
                          , &frontal[2], &frontal[3], &frontal[4], &frontal[5], &premolar[2], &premolar[3], &molar[3]
                          , &molar[4], &molar[5] };

In this example the arrays would be destroyed upon leaving the context. With your use of a pointer array and the new constructor you can keep the array in use after the method finishes, but keep track of it and delete it when you no longer need it, aka in the destructor. The simpler way is to define the same "owner arrays" as in my example, in the Header of the main class.

And finally for the logic bit at the bottom, this is what I replaced your code with:

   int x = 0;     // For the position of the tooth on the screen;
   int y = 0;     // Why not a y variable as well?
   int texture = 0;    // We will need it to assign the textures in order;

   for(int i = 0; i<16; i++) {
       upperTeeth[i]->setPositionX(i + x); // Some function for setting the position on the screen from left to right;

       if (i < 8) texture++;     // Setting the textures for the teeth on the left;
       if (i > 8) texture--;    // Setting the textures for the teeth on the right(the same, but in reverse order);

       upperTeeth[i]->setTexture(texture); // alternative to your constructor

       upperTeeth[i]->setPositionY(y);
   }

Watch that you missed using the [i] instead of [16]. Alternatively, you can directly loop through your array using

 int i = 0;
 for (auto tooth : upperTeeth) {
      tooth->setPositionX(i); // you still need i for your case.
      i++;
 }

Hope I managed to clear up some things!

Maxime Franchot
  • 1,015
  • 1
  • 10
  • 24
1

I finally did what I wanted to. The problem was purely logical. And to all of you, who say I shouldn't subclass - I already made a version of the program, in which there are no derived classes of tooth, and at some point I felt it way too limiting. Now I'm posting the code that worked:

    class Tooth 
    { 
       public:
       Tooth(Texture &texture);
    };
    
    class Molar : public Tooth { };
    class Premolar : public Tooth { };
    class Frontal : public Tooth { };
    
    main()
    {
       Molar *molar[6];
       Premolar *premolar[4];
       Frontal *frontal[6];
    
           Texture texture[8];   //only 8 textures, since the other half of the teeth are mirrored
         
           int toothindex[16] = { 0, 1, 2, 0, 1, 0, 1, 2, 3, 4, 5, 2, 3, 3, 4, 5 };
        
           int textureindex = 0;    //we will need it to assign the textures in order;
        
           for(int i = 0; i<16; i++)
           {
               if(i < 3 || i>12) 
               molar[toothindex[i]] = new Molar(texture[index]);
            
               if(i==3; i==4; i==11; i==12)
               premolar[toothindex[i]] = new Premolar(texture[textureindex]);
    
              if(i>4 && i<12) frontal[toothindex[i]] = new Frontal(texture[textureindex])
    
           if(i<7) texture++; 
           if(i>7) texture--;  
          }
   }  
       

Thanks for telling me about the so called undefined behavior and proposing the if statement. All I had to do was to create an array of ints, and use it to initialize the specific classes in the for loop. Now it looks something like this: https://i.stack.imgur.com/0yZv5.jpg

Oh... and since I'm using array of POINTERS, for anyone who seeks the answer, don't forget to make some kind of for loop to delete the objects, when you dont need them:

for (int i = 0; i<6; i++)
{
    if(i<4) delete premolar[i];
    delete molar[i];
    delete frontal[i];
}
TheFinalCutBG
  • 43
  • 1
  • 6
  • I wanted to add, regarding this style.. You could use a `dynamic_cast` instead of directly knowing the index of the array, if the array is already full. So like `if (dynamic_cast(upperTeeth[i]) != nullptr)` will only go through if the item at `upperTeeth[i]` can actually be casted as `premolar*`. I always use this kind of type checking. – Maxime Franchot Jul 16 '20 at 14:08
  • 1
    Haven't learned about casting so far, so I have no idea what it is (I started coding 3 months ago because of the lockdown :D) I will check that out! – TheFinalCutBG Jul 18 '20 at 00:01