Explanation needed regarding base class Ctor usage

As many of you know .... going through Stroustrup's PPP text book.

And in it he has the following defined structure that is a sub-class to the base class: Shape. Here's what the declaration looks like:

1
2
3
  struct Open_polyline : Shape{
       using Shape::Shape;  \\use shape's constructors and facilities
       void add(Point p){Shape::add(p);}   \\WHY DOES HE DO THIS? 


Why does he explicitly use Shape's add() method when Open_polyline already has access to this method.

Why didn't (or COULDN'T) he just leave it as==>

void add(Point p); and be done with it? Why go through the base class when the inherited class already has access to this method facility??

Xanadu
Last edited on
When you post something about the book, it would help if some citation data were included so we can find it. Some may not have the PDF version that might be searchable.

That said, in shape, the add( Point ) function is protected. While the derived class can access it, no user code could access add(Point). This makes it publicly accessible.

Later, in a different derived class (Line I think), add( Point, Point ) takes two points, then calls "add" twice, once for each point.

Keep in mind this is illustrative, not necessarily the best design example.

Stroustrup has mentioned several times in lecture to avoid using protected, favoring either private or public, with rare exceptions.



Last edited on
Niccolo,

Sorry about that. I will definitely site references from now on rather than just discussing "blind".

Ok, so he made it protected and that limits the accessibility of the user code but not derived objects? Am I getting that correctly?

Is what I am seeing above: explicitly making the derived class to use the protected add() method since that is really the only way to access it according to what you are saying?? (Since no user code can really touch it?)

Yes, I haven't messed with protected too much and only have experience (limited and beginner at that!) of private and public like you are explaining.

So Niccolo, is he purposely doing this (design methodology) again to prove a point and kind of show the student sort of what NOT to do? Or is he doing this out of pragmatism because he hasn't covered pointers, inferred types, or some C++ concept that would make it unnecessary to do this or at least not optimized to do this?

As a seasoned user, have you run into a lot of situations in your career where protected definitely made the difference and there was no other way of pulling off whatever you were doing?

Xanadu
Ok, so he made it protected and that limits the accessibility of the user code but not derived objects? Am I getting that correctly?

I think you have the gist of it.

This is useful for situations where you want to allow the author of a derived class to be able to use it and possibly override it, but otherwise don't want every Thomas, Richard, and Harold to be able to use it.

For an open polyline, it actually does make sense for anyone to just be able to add points to the shape, hence it's made public here. Not so for a Triangle, Circle, or so on.

As a seasoned user, have you run into a lot of situations in your career where protected definitely made the difference and there was no other way of pulling off whatever you were doing?

Of course not, but then again we don't definitely need any form of access control, per-se. It's a tool to stop other programmers (including future you) from using code in ways that present you did not intend, at least not without reviewing and modifying said code.

-Albatross
Last edited on
@Albatross

(including future you)


I'm so glad I'm not the only one that noticed my future self is someone else (with respect to writing software)

@Xanadu4ever,

I've almost never use protected. However, it is in the language and Stroustrup intends to exercise the language in your hands. Though he uses a bit of the "Socratic method" (self learning, get into trouble and get yourself out of it), there's no real hidden point there.

Like @Albatross said, the points of a triangle and such are not to be added, but for a polyline of some kind, well, that's what it is. Thus, an interface in the base applicable to both situations (for the derived classes being developed).

The idea is to show the difference between a user's interface (the future you and others) and interior interface (the derived classes).

Protected seems more appropriate for designs where the intent is for users to derive from a class for their usage cases, but over the years that concept became less used than it was early on. New language features offered better options.


Albatross sand Niccolo,

Thank you for the insight on this. I'm sure I'll need and get more exposure to the DIFFERENCES as I go along and this will become more clearer. Ok, this makes sense for the most part, Especially what you said Niccolo about Sroustrup's use of the Socratic method when it comes to this type of learning. It is very powerful - but also frustrating when he purposely leaves out details or doesn't (at THIS point in the book at least) go into more detail about why you would want to or NOT want to do something and what exceptions there are. The exceptions are the pain in the butt for new C++ users I believe.

Niccolo, you also said that:
.....but over the years that concept became less used than it was early on. New language features offered better options.

What are the new language features that make this "out-of-style" and what have they done to improve this situation?

Xanadu
@Xanadu4ever,

On the subject of inheritance as a model for frameworks waning over the years:

The early implementation of C++ did not have templates, but it did have virtual functions. In C it was possible to call functions via pointers to functions, and the virtual function put that tendency on steroids.

If you've used any GUI framework you're probably familiar with the implementation of a user interface to a framework using virtual base classes. To create a window or control, the user inherits from one of the framework's classes. This provides an interface in the base to the user's class, and can impose requirements upon the user through pure virtual functions.

When templates did not exist, this was also the way containers and reference counted pointers were implemented. In these frameworks the user would inherit their classes from an "object" base class (it was just common to use the name object in some form). This is known as an intrusive pointer or intrusive node (like the nodes of a tree or linked list).

When templates were first introduced most of us writing in C++ set about making our own smart pointers. Using templates this meant the target classes did not have much of any requirement placed upon them (didn't require inheritance from "object"). This was immediately extended by a personal library of containers. Many were awful, flawed, but useful. This was before the STL was released (which was among the better, if not the best, of these kinds of project implied by the very existence of templates).

Eventually a number of interesting use cases of template design became design patterns. A book by Alexandrescu from 2001 demonstrated policy based designs. It is loosely related to the "curiously repeating template pattern" (CRTP) pattern. Policies look like this:

template < typename T > class A : public T {};

The key here is that the class from which this derives it the parameter to the template. This can impose base class interface and behavior upon A, but it can mutate by user selection. In a way this returns the motivation for virtual functions, or intrusive base class components (like "object").

The CRTP pattern is related but differs in this way:

1
2
3
template <typename T> class B {}; // "T" may be the "receiver" of this design pattern
 
class D : public B<Derived> {};      // the base "knows" the derived type natively now 


You can see the similarity, but now the base can call functions in the derived class directly. It is impractical to make a base class without templates like this (may even seem absurd). The point, here, is that "B" can mutate based on "D", but impose no virtual functions, giving a runtime performance benefit. These two methods perform a compile time "morphism" selected by the consumer of the object(s).

These are but a few paradigms that have emerged over the years which provide varied ways of implementing the kind of interface to a framework, or class, which have different performance or compliance features upon the user's code.

Going back to reference counted pointers, the old style, inheriting from "object", put the "knowledge" of reference counting in the base of the object. This meant that a single allocation created one instance with both the reference counting logic (and data), but also included the user's code and data in one object.

The first smart pointers using templates, which did not require the user class to inherit from any base and basically imposed on particular requirements on the class, couldn't do that. The initial formations everyone created, included the original shared_ptr, put the "node" in it's own instance (this held the reference count and logic). The node "owned" the user's instance through a pointer. That was, therefore, two allocations for every user instance. One of the "node", the other for the user's instance that node controlled. In case this isn't already familiar, the smart pointer itself "pointed to" the node (usually as an instance on the stack or as a member of a class).

This was extended by the use of "in place new" and explicit call to the destructor, to create a "node" object which also provided storage for the user's instance in one allocation. You may know this as "make_shared", "make_unique" and "make_scoped".

Containers have similar transformations due to templates, but until C++11 there was a minor issue. For most containers the object had to be copyable. If not, you only way the user to store instances in the container was through a pointer (so the pointers could be copied instead of the instances). Adding "move semantics" to the language fixed that problem (you just have to move instead of copy).

I'm half through the max post limit, and my car's timing belt replacement beckons me to mechanic's duty, but if I've missed any particular highlight on this subject it may come from other posts or further exchange. You may be able to figure some out yourself, too.

(Geeze, I got into electronics and computer programming just so I didn't have to end up with grease from my fingers to my elbows, but I refuse to pay $2700 to someone else to do something I can complete for $200 in parts, especially for a 2004 car.)


Niccolo,

Again as usual you do NOT disappoint when providing a REAL explanation of what is going on in C++! With that said, I am going to TRY to converse with you on these matters in a non-ignorant fashion - but I am still very ignorant of many things that you are discussing here.

If you've used any GUI framework you're probably familiar with the implementation of a user interface to a framework using virtual base classes.

I absolutely AM familiar with these concepts now but not an expert of course. And Bjarne described that inheriting from the base to implement my own designs seems to be the norm with this. The virtual functions and "pure" virtual designs for a base class that is designed ONLY to be inherited from is what Stroustrup JUST introduced in chapter 14. So I am tracking with you so far.

Templates and pointers and non- "runtime Polymorphism" still have to be introduced as well as the situation of "smart pointers" and keeping track of memory allocation from the Stroustrup perspective. But what you are saying I can understand from a general point of view at the moment but it is still abstract and not concrete since I have yet to actually "use" these mechanisms - but I DO see where this is going from your explanation.

So, the modern C++ user uses LESS run-time polymorphism and inheritance from virtual functions in this manner and INSTEAD shifts this to a mechanism where templates are used instead? The whole concept that you stated above==>
......now the base can call functions in the derived class directly. It is impractical to make a base class without templates like this (may even seem absurd). The point, here, is that "B" can mutate based on "D", but impose no virtual functions, giving a runtime performance benefit.

Is strange because this implies the super-class is CHANGED by the act of changing the sub-class and naturally the sub-class is changed by changing the super-class as well? Did I understand you correctly here? This "two-way street" morphism is alien to my thought process at the moment and I am only left wondering at the implications in the code to this point.

On a different note, (and then I will close this post out as "solved") I ran into something strange that I THINK is related to this but perhaps not! When I was creating a sub-class for chapter 13 exercises, I noticed that when I attempted to define the sub class as a literal "class" while the base was in fact a "struct" (Bjarne says these are synonymous? The compiler didn't seem to think so!) - the compiler complained bitterly about this - until I changed the sub-class definition to a "struct" as well and then it seemed satisfied with the re-definition.

Any thoughts as to why this might be the case?

Xanadu
class and struct are very nearly synonymous, but not exactly, and the difference is important. I find it hard to believe Stroustrup doesn't explain the difference, but if he doesn't, I'm sure a simple Google search will get you that information.

As such, it's only right that the compiler gives an error if you're inconsistent in how you define/declare your types.
Last edited on
Mikey,

Thank you for that head's up and it is good you said that. I most certainly WILL look up the differences and to answer you directly - NO, Strousstrup does NOT (at this stage in the book at least) point out that the two (structs and classes) are different enough that you should NOT conflate them and they can NOT be used interchangeably. He states that they are "nearly" identical and hence implies there is some difference - but doesn't offer what those differences are and doesn't not give caution to the would-be that would try to make them interchangeable.

So to make this painfully NOOB obvious: If a user defined type derives from another type - if that base type is a "class" can the children be "classes" or "structs"? Or, said another way==> is it more like if the base is a "struct" it won't allow children to be "classes" BUT if the base is a "class" the children can be "structs" or "classes"??

Am I making this TOO simplified for my own good or is that about right?

Xanadu
There is only 1 difference between a class and a struct. Just one. No other :)

It is that in a class, members default to private until declared public. In a struct, it is the reverse.

There is no restriction deriving a class from a struct, or a struct from a class, but if one doesn't take care of the matter of public and private designation it could seem there are problems.

One of the use cases that comes up with early experimentation involves forward declaration. If a forward declaration appears to declare, say, struct C; Just that, declaring that C is a struct, but doesn't say anything else. If later the actual declaration is class C, there will be a compiler complaint. If someone experimenting with code has a forward declaration, then changes between struct and class, this can be an issue - one must change all declarations, forward or full, to match.


Is strange because this implies the super-class is CHANGED by the act of changing the sub-class and naturally the sub-class is changed by changing the super-class as well?


This does come into play, though you might get argument from some. In policies, for example, where the base parameter is changed, that can change the requirements of the derived (where the base provides new pure virtuals), and calls made by the derived may not be supported in the base. In well designed policies this doesn't happen in practice (because the interfaces in either direction should be 'normalized' to fit together properly), but..."stuff" happens :)


Niccolo,

I get it now. It depends on how I declare the members. I suppose (not sure WHY I would do this..??) that someone could declare a structure and have all of the data members private and the methods public (or protected I suppose) and this would literally function just like a default "class" definition.

Then, I suppose the error was that I had declared the class and NOT specified the methods that I was trying to use as "public" and therefore the compiler naturally called me on this rather blatant error when I tried to invoke methods that were clearly off-limits until they were explicitly made public.

Your words are NOT reassuring Niccolo when it comes to the difference between what actually transpires in reality as opposed to "theory"! LOL.

I'll keep a sharp eye out for this stuff though and I am sure the conversations of what seems like many subtle but far reaching implications of C++ nuances as the language evolves will continue for quite some time!

Thank God for support pages like these where people can actually point you away from the changes in the language that pretty much signify: "Bridge out! Detour. Try alternate route."

Xanadu
Topic archived. No new replies allowed.