Suggestion

  • Thread starter Eivind Grimsby Haarr
  • Start date
E

Eivind Grimsby Haarr

When using polymorphism in a big system, I have sometimes come across
problems when changing the signature of the function in the base class,
and forgetting to change the signature of the derived functions
accordingly. (Most often when only some of the derived classes redefine
it.) Accessing the objects with base class pointers, the version in the
base class is called, which is not what I wanted. This is most often very
difficult to detect.

This has made me come up with an idea for a compiler/language feature that
would fix this problem: A keyword in front of the derived function that
says that this function is derived from a base class, and the compiler
should issue an error if a function with a similar signature doesn't
exist in one of the base classes.

What do you think about this idea? Is it just too much pain for a rare
problem?

Maybe anyone have other ideas on how to prevent such problems? Using
macros to declare the signatures is of course one idea, but true
C++-programmers don't like macros, right? (Perfect design and
programming from the beginning would prevent it, of course, but even
programmers are humans...)

Naming the keyword 'isderived', I'll illustrate my idea with an example:

Function f(double) is performing some calculation. In the base class B, a
default calculation is provided, but a few of the many classes that derive
from B has their own implementation.

-------

class B {
virtual double f(double i) {...} // Uups! Forgot to declare 'const'
}

class OneOfManyDerivedClasses : public B {
isderived double f(double i) {...} // Should be derived from base class
}

vector<B*> v;
....
// Fill the vector with objects of different derived classes
....
int sum = 0;
for (it = v.begin(); it != v.end(); ++it) {
sum+=(*v)->f(number);
}

------

If I later change the base class function to const, the function in the
derived class will not be called when iterating through the vector. If
this calculation is part of a complex calculation with many classes, this
is difficult to detect. With the 'isderived' keyword, a compiler error
would be issued, and the problem prevented.


Any views?


Mvh
Eivind Grimsby Haarr

[ 97 07 66 58 / (e-mail address removed) ]
 
P

Phlip

Eivind said:
When using polymorphism in a big system, I have sometimes come across
problems when changing the signature of the function in the base class,
and forgetting to change the signature of the derived functions
accordingly. (Most often when only some of the derived classes redefine
it.) Accessing the objects with base class pointers, the version in the
base class is called, which is not what I wanted. This is most often very
difficult to detect.

This has made me come up with an idea for a compiler/language feature that
would fix this problem:

Wouldn't that mistake break all the unit tests that depended on the base
class polymorphing into the derived class's behavior?
 
U

Unforgiven

Eivind Grimsby Haarr said:
When using polymorphism in a big system, I have sometimes come across
problems when changing the signature of the function in the base class,
and forgetting to change the signature of the derived functions
accordingly. (Most often when only some of the derived classes redefine
it.) Accessing the objects with base class pointers, the version in the
base class is called, which is not what I wanted. This is most often very
difficult to detect.

This has made me come up with an idea for a compiler/language feature that
would fix this problem: A keyword in front of the derived function that
says that this function is derived from a base class, and the compiler
should issue an error if a function with a similar signature doesn't
exist in one of the base classes.

What do you think about this idea? Is it just too much pain for a rare
problem?

Someone had your idea already. ^_^
The following samples follow VB.NET syntax.

Public Class Base
Public Overridable Sub SomeMethod(ByVal a As Integer)
' Stuff
End Sub
End Class

Public Class Child
Inherits Base
Public Overrides Sub SomeMethod(ByVal a As Integer)
' Stuff
End Sub
End Class

As you can see, the "Overrides" keyword does what you describe. Leave it
out, and the compiler will tell you "sub 'SomeMethod' shadows an overridable
method in a base class. To override the base method, this method must be
declared 'Overrides'." This is a warning not an error, but if ignored, as
the message indicates, the method will shadow (hide) the base class method,
not override it, which means any polymorphic calls to a Base reference that
is really a Child will always access the Base SomeMethod. If that's the
behaviour you want but want to get rid of the warning, you should add the
"Shadows" keyword, which tells the compiler explicitly that that is what you
want to do.

If you were to change the signature of SomeMethod in the base class (with
Overrides on the child class method declaration), for instance have
parameter "a" be a Double instead of an Integer, the compiler will give you
both the error "sub 'SomeMethod' cannot be declared 'Overrides' because it
does not override a sub in a base class." and the warning "sub 'SomeMethod'
shadows an overloadable member declared in the base class 'Base'. If you
want to overload the base method, this method must be declared 'Overloads'."

As you can see, you're not the only one who thinks this kind of behaviour
would be a good idea. I know it's one of the VB.NET language features I
appreciate. ^_^
 
U

Unforgiven

Phlip said:
Wouldn't that mistake break all the unit tests that depended on the base
class polymorphing into the derived class's behavior?

Prolly, but that's only *if* you write unit tests ^_~

(And don't anyone dare reply you should always write unit tests, I was being
sarcastic damnit!)
 
D

David

When using polymorphism in a big system, I have sometimes come across
problems when changing the signature of the function in the base class,
and forgetting to change the signature of the derived functions
accordingly. (Most often when only some of the derived classes redefine
it.) Accessing the objects with base class pointers, the version in the
base class is called, which is not what I wanted. This is most often very
difficult to detect.

My solution, is that the person(s) responsible for the change have to
insure their change does not adversely affect the rest of the system.
For a big system, this may mean examining all the source or issuing
some kind of announcement to a development group. Since it sounds like
you have control of the system and the ability to do a full compile,
it should have been possible to find all uses and verify that your
proposed change could safely be made. On occasion, I've intentionally
broken a method to help find all its users during a local build.

For the "test case" crowd, it might be sufficient to change some of
the tests to insure compatibility. However, I can imagine times where
even a simple change could modify an entire test hierarchy. This is
where I've seen a few TDD groups fall short from inexperience. It can
be difficult to make such a change and then find all the uses and
implications of that change.

<snip>

David
 
P

Phlip

David said:
My solution, is that the person(s) responsible for the change have to
insure their change does not adversely affect the rest of the system.

ensure ;-)
For a big system, this may mean examining all the source or issuing
some kind of announcement to a development group. Since it sounds like
you have control of the system and the ability to do a full compile,
it should have been possible to find all uses and verify that your
proposed change could safely be made. On occasion, I've intentionally
broken a method to help find all its users during a local build.

A symetric typographical error could create the issue. Then you don't know
you should announce or rebuild.
For the "test case" crowd, it might be sufficient to change some of
the tests to insure compatibility. However, I can imagine times where
even a simple change could modify an entire test hierarchy. This is
where I've seen a few TDD groups fall short from inexperience.

"groups"? You mean TDD-ing teams?

I use TDD to generate behavior, then I refactor that behavior into an object
model. The behaviors might go here or there, but the tests that created each
one still (in theory) demand them to exist. So (in theory), if that behavior
now routes thru virtual functions, the tests would detect failure if the
virtuality breaks.

I could look at my C++ TDD programs that inherit, and see if breaking the
virtual link will break a test. In theory.

In practice, one can use the Abstract Test pattern to mirror production type
hierarchies to test hierarchies. However, they tend to prove that all
derived classes behaved the same, not different.
It can
be difficult to make such a change and then find all the uses and
implications of that change.

Were all those uses themselves invented via TDD?
 
A

Alf P. Steinbach

* Eivind Grimsby Haarr:
When using polymorphism in a big system, I have sometimes come across
problems when changing the signature of the function in the base class,
and forgetting to change the signature of the derived functions
accordingly. (Most often when only some of the derived classes redefine
it.) Accessing the objects with base class pointers, the version in the
base class is called, which is not what I wanted. This is most often very
difficult to detect.

This has made me come up with an idea for a compiler/language feature that
would fix this problem: A keyword in front of the derived function that
says that this function is derived from a base class, and the compiler
should issue an error if a function with a similar signature doesn't
exist in one of the base classes.

What do you think about this idea? Is it just too much pain for a rare
problem?

It's always a good idea to catch errors at the earliest time possible,
preferably analysis and design, but if not, then compile-time.

Your idea is a Good One (TM) and is implemented in .NET languages such as
VB.NET and C#, in the latter using the keywords "override" (override) and
"new" (shadow).

Getting it into C++, that's another matter... ;-)
 
A

Aguilar, James

Eivind Grimsby Haarr said:

It would be an interesting exercise to write a C++ program that would
recurse into a directory structure and find all instances of a method
(search for : public \ClassName/ then find the method (if it exists)
within), and replace them. You could call it "rename". =)

I don't know if you have time, but it seems like a pro would not take all
that long to whip up something like this, even if it is platform dependent.

James
 
P

Phlip

It would be an interesting exercise to write a C++ program that would
recurse into a directory structure and find all instances of a method
(search for : public \ClassName/ then find the method (if it exists)
within), and replace them. You could call it "rename". =)

I don't know if you have time, but it seems like a pro would not take all
that long to whip up something like this, even if it is platform
dependent.

It's the first feature of a "refactoring browser" - rename identifier. To
learn how "easy" that is, search the net for a refactoring browser for C++.
 
A

Aguilar, James

Phlip said:
It's the first feature of a "refactoring browser" - rename identifier. To
learn how "easy" that is, search the net for a refactoring browser for
C++.

I'm making an assumption here that might not be correct, so correct me if
I'm wrong -but- I assume that you are trying to imply that it is not easy (I
don't really want to use my time to search for such a browser right now,
since I personally have no use for it at the moment). Maybe you can explain
why?

In any case, here's my idea for an algorithm:

ALGORITHM :

Inputs: Method signature, set of type name,
directory name *possibly blank for base level call*
Vars: boolean valid (begins false)
Returns: boolean
WHILE (valid) {
0. valid = true;
1. Get all files in this folder **PLATFORM DEPENDENT**
a. remove all files that do not end in extension .cc or .hh (GNU naming
conventions)
2. For each file
a. Continue to eat input until you come to end of file or come to ":
public
\member of set/" or "\methSig&member of set/" (use tokens to process
the input,
basic stuff)
b. If you come to the case described in 2a., valid becomes false and do
step 3,
otherwise, step four
3. Edit "\methSig/" as appropriate and write the file
4. Get list of directories and recurse. If any recursive call returns
false, valid is
set to false.
}

: ALGORITHM

So that would do the part of making sure that within the classes, everything
is changed. The reason for valid is to make sure that if there is a
subclass of a subclass, it is included. It would not rename calls to the
method or check to make sure that the change did not break outside uses of
it, but I think this would accomplish what the OP wants (that is, to prevent
any silent errors from showing up and biting him in the butt later on). It
doesn't seem like it should be -that- hard, but I didn't mean to say it
would be easy, especially for someone of my age in the processes of learning
this stuff.
 
P

Phlip

Phlip wrote:

I'm making an assumption here that might not be correct, so correct me if
I'm wrong -but- I assume that you are trying to imply that it is not easy (I
don't really want to use my time to search for such a browser right now,
since I personally have no use for it at the moment). Maybe you can explain
why?

I have never tried it. However...
In any case, here's my idea for an algorithm:

ALGORITHM :

Inputs: Method signature, set of type name,
directory name *possibly blank for base level call*
Vars: boolean valid (begins false)
Returns: boolean
WHILE (valid) {
0. valid = true;
1. Get all files in this folder **PLATFORM DEPENDENT**
a. remove all files that do not end in extension .cc or .hh (GNU naming
conventions)
2. For each file
a. Continue to eat input until you come to end of file or come to ":
public
\member of set/" or "\methSig&member of set/" (use tokens to process
the input,
basic stuff)
b. If you come to the case described in 2a., valid becomes false and do
step 3,
otherwise, step four
3. Edit "\methSig/" as appropriate and write the file
4. Get list of directories and recurse. If any recursive call returns
false, valid is
set to false.
}

: ALGORITHM

The first time that algorithm renames an innocent bystander, you will feel
bad, and stop using it.

When folks use a refactoring browser in a softer language that permits them,
they fly, because the browser cannot make a mistake.

My C++ code routinely screws up the intellisense and code browsing system in
my Visual Studio editors. They use "just a little language parser" like you
recommend. They don't use the real C++ compiler's program database, because
they want to change their browsing on the fly as I add statements. So we get
the worst of both worlds. My intellisense breaks when the wind blows, and my
browser can't find variables that the compiler can.

If a C++ refactoring browser were this unstable, nobody could tell when it
was adding bugs, so nobody would use it.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top