Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

OOD idea of C++ and class coding guide lines

119 views
Skip to first unread message

wij

unread,
Jun 28, 2023, 4:36:40 AM6/28/23
to
The following are rewrite of my 20-years old coding guide lines. I'd like to
ask for suggestions to refine in the way because one reason is that recently
the many questions I saw in this forum are actually rooted in class design
problems (or problems of being too expressive, arbitrary).

------
I think one major feature C++ provides is the support (or restriction) of
Object-oriented Design. From the wiki https://en.wikipedia.org/wiki/Object-oriented_design
or others, the definition is a bit difficult to be practically applicable. And
the 'isa' part ideal of OOD I knew didn't properly work.

In addition, C++ class actually works by 'semantics' (or convention) which is
not mentioned anywhere (E.g. default constructor,copy constructor and operator=).
Thus, the following guide lines are defined (exceptions are few):

Constructor(..):
Constructor forms are in basic the Cartesian product view of problem domain
in tupple. IOW, the arguments of ctors define the class (concept).
Constructor brings an object in random state to responsible state.

Destructor:
The destructed object is no longer considered existing in the C++ language
(ref. [12.4.14]). Since destructing an object the second time results to
undefined behavior, destructor must make sure destructing it once is enough.
In another word, destructor must succeed to the language. Object destruction
actually magnifies the effect of common ignorance of the implicitly created
rollback function (or a design).... The program execution from return/throw/
exit.. to its caller(e.g. main, init..) shall succeed.

const member functions:
Const members describe the property of the class. Normally, these members
should be in O(1) complexity. And, if properties of two objects are equal,
the objects should behave the same.

Reset(..) members:
Reset members brings the object from responsible state back to random state
and then does whatever the argument corresponding constructor does.
Therefore, If a reset member exists then the argument(s) corresponding
constructor also exists.

Construct/destruct/reset are implemented as composite operations of object
initialization process of the life cycle view (and the symmetrical reverse).
Implementation can optimize the theoretical sequence.

Since this library adopts an implicit rule that object has to be in a 'valid'
state (no good in class Array), the reset() postcondition is thus required to
be default. Default state is among the easiest check points to ensure object
constructed, in whatever valid state it may be, can always be successfully
destructed. Note that reset members normally exist but not necessary. For
instance, if the class contains const or reference data members, then the
reset would be difficult to implement.

Move Constructor:
The motivation of devising is from the need to avoid costly and unnecessary
copy operations found in classes that contain allocated resources and the
objects movement in a dynamic array. It has been practiced that such
motivation basically just need a *move constructor*. This library uses
"enum ByMove_t { ByMove }" as the signature denoting a move constructor (for
the moment), which is defined to move the source referenced object to 'this'
pointed address (source object is treated non-existent). The reason is that
such an operation is conceptually elementary, thus can enable the definition
of a no-throw swap function template (note: implementation needs a buffer
type, e.g. type_store<T>, but this is a different issue).

Many other libraries do not contain the move constructor. Bit-wise copy
(memcpy) may mostly do the job, e.g. QString of Qt library, so far. Just
make sure that destructor of the moved object won't be called.

Note: C++11 introduced rv-reference and defined another name 'std::move',
a constructor taking a rv-reference argument is therefore called a move
constructor. This library decided to continue the development using this now
standard-confusing move constructor. Simply because ByMove is relatively
more elementary. Many reasons can eventually be translated to being light
weight, which in a way means it can transform easier. Again, This library's
guide "No room should be left for lower-level implement". Another reason is
the ratio of gain v.s. cost does not appear good for the author to use it,
hopefully this library can survive.

-------- Class member rules
Let T denote a given class of this library. The following member names
and associated functionality are defined.

T() Default constructor. The object thus construted is referred
to as in default state and so the default object.

Postcondition of throw: object does not exist.

Note: If object can mean 'no real contents', the default object is
better thus designed, at least to be safely destructable.

Errno reset(...)
Reconstruct the object to the state as constructed.
If reset(..) defined, there exists the argument corresponding
constructor (reset() usually exists)

Ex: reset members should function identical to the following
example:
Errno T::reset(Type1 a, Type2 b) try {
T obj(a,b);
swap(*this,obj);
return Ok;
}
catch(const Errno& e) {
return e;
};

Note: This rule is important. Real practice may want do things
slightly different in some cases, but no, hidden, hard to find
bugs may exist.
Note: For reset() (no argument), object return state (and Reply) is
always default.

~T Destruct object

Postcondition: object does not exist

bool is_default() const
return true iff object is equivalent to the default constructed
object (i.e a.is_default() <==> a==T(), if operator== is defined).

bool operator==(const T& rhs) const
This function returns true iff *this and rhs are not
distinguishable by using any non-private member on the same
condition unless otherwise explicitly specified.

Note: Object equivalence does not include equivalence of SLI and
address of its own.

T& operator=(const T&)
Reconstruct object to the state as the copy constructor constructed
(same as reset(const T&) except the return type and throwing error).

void swap(&T)
Exchange object state of *this and the argument indicated object.

Take 'a.swap(b)' (commutative) for instance, the state of a is set
to the state of b. The previous state of a becomes the state of b.
The same applies for b

Note: No construct and destruct semantics involved

Reply
Class specific throw class inherited from Wy::Errno.

_.. prefix for system-specific members or candidate, or restricted

wy_.. prefix for internal members (for test codes, etc.).

------

wij

unread,
Jul 2, 2023, 3:27:55 AM7/2/23
to
I was noticed C++ has its (language?) guidelines:
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
Lots of 'vision' and talks (contradictory) in the CppCoreGuidelines, but I
admit the fact that stdc++ faces lots more challenge than I can handle.
libwy focuses on OO way of problem solving, the implement is not relatively so
important (stdc++ is same, not really the fast kind of 'efficiency'), the result
might be paving the road for a new language.

Comparing libwy with the standard library, the prominent differences are:

Stream I/O:
libwy relies on 'syscalls' to accomplish the goal of being a library for
system programming (stdc++ seems to have abandoned this goal). And, the
guideline "No room should be left for lower-level implement" found in Bjarne
Stroustrup's book is realized in this way.

Error handling:
libwy uses Errno (and macros containing __FILE__, __LINE__ to indicate 'where')
for returning and throwing error report. Because, 'devising errors' has many
realistic problems (ref. the evolution of errno).

I think these two are already difficult for stdc++ to solve. And, a nearby post
reminded me that I had tried a similar idea to std::filesystem::path
https://en.cppreference.com/w/cpp/filesystem/path
...I thought if it could be satisfactorily solved like that, it would be
equivalent to that stdc++ invented a general OS.
Lots things I don't know, it seems all going back the beginning of C++.

wij

unread,
Jul 3, 2023, 12:32:17 PM7/3/23
to
The following is the updated guideline (file ClassGuideLines.txt), posted for
review. Subtleness is there, don't be fooled by simplicity.

----- ClassGuideLines.txt

This file updates the class guideline in Rationale.txt
The guideline focuses on the OO way of class (concept) design, and is made to
ensure that the class (concept) design is provably consistent.

+----------------------------------------------------------+
| Sequence of acquisition incident: Object life cycle view |
+----------------------------------------------------------+
1. Name binding
(there might be anonymous object)
2. Size binding
Information to allocate memory
3. Address binding
Object in this stage is called in random state.
4. Semantics binding
Object is in responsible state of the occupied space and contents.
(external resource may be associated in the semantics)
5. Reversal is the object destruct process

Note: Class of objects whose destructor can be runtime ignored is referred
to as discardable in this library. Often, such class has no user
defined destructor and such objects can be overwritten.

----Result of the life cycle view is the basic rule of class members

Constructor(..):
Constructor forms are in basic the Cartesian product view of problem domain
in tupple. IOW, the class (concept) is defined by the arguments of ctor.
Constructor brings an object in random state to responsible state.

Destructor:
The destructed object is no longer considered existing in the C++ language
(ref. [12.4.14]). Since destructing an object the second time results to
undefined behavior, destructor must make sure destructing it once is enough.
In another word, destructor must succeed to the language. Object destruction
actually magnifies the effect of common ignorance of the implicitly created
rollback function (or a design).... The program execution from return/throw/
exit.. to its caller(e.g. main, init..) shall succeed.

Note: C++ language had enforced the semantics that 'destructor must succeed'.

const member functions:
Const members define the property of the class. Normally, these 'property'
members should be in O(1) complexity. If properties of two objects are equal,
the objects should behave the same.
These members provide the basic proofs that the class is properly designed.

Reset(..) members:
Reset members brings the object from responsible state back to random state
and then does whatever the argument corresponding constructor does.
Therefore, If a reset member exists then the argument(s) corresponding
constructor also exists.

Construct/destruct/reset are implemented as composite operations of object
initialization process of the life cycle view (and the symmetrical reverse).
Implementation can optimize the theoretical sequence.

Since this library adopts an implicit rule that object has to be in a 'valid'
state (no good in class like Array), the reset() postcondition is thus
required to be default. Default state is among the easiest check points to
ensure object constructed, in whatever valid state, can always be
successfully destructed. Note that reset members normally exist but not
necessary. For instance, if the class contains const or reference data
members, then the reset would be difficult to implement.

Move Constructor:
The motivation of devising is from the need to avoid costly and unnecessary
copy operations found in classes that contain allocated resources and the
objects movement in a dynamic array. It has been practiced that such
motivation basically just need a *move constructor*. This library uses
"enum ByMove_t { ByMove }" as the signature denoting a move constructor (for
the moment), which is defined to move the source referenced object to 'this'
pointed address (source object is treated non-existent). The reason is that
such an operation is conceptually elementary, thus, it can enable the
definition of a no-throw swap function template (note: implementation needs
a buffer type, e.g. type_store<T>, but this is a different issue).

Many other libraries do not contain the move constructor. Bit-wise copy
(memcpy) may mostly do the job, e.g. QString of Qt library, so far. Just
make sure that destructor of the moved object won't be called.

Note: C++11 introduced rv-reference and defined another name 'std::move',
a constructor taking a rv-reference argument is therefore called a move
constructor. This library decided to continue the development using this now
standard-confusing move constructor. Simply because ByMove is relatively
more elementary. Many reasons can eventually be translated to being light
weight, which in a way means it can transform easier. Again, This library's
guide "No room should be left for lower-level implement". Another reason is
the ratio of gain vs. cost does not appear good for the author to use it,
hopefully this library can survive.

-------- Class member rules
The general rule: Class members should be able to conduct self-check for
correctness, except not possible.

Let T denote a given class of this library. The following member names
and associated functionality are defined.

T() Default constructor. The object thus construted is referred
to as in default state and so the default object.

Postcondition of throw: object does not exist.

Note: If object can mean 'no real contents', the default object is
better thus designed, at least to be safely destructable.

T(const T&) Construct *this to the state as the source object.
(nothing more to say from general practice)
T(T&, ByMove_t) Move the source object to 'this' pointed address.
This member is not really a constructor since nothing is created.

Errno reset(...)
Reconstruct the object to the state as constructed by the arguments
corresponding constructor.

Ex: reset members should ideally function identical to the following
example:
Errno T::reset(Type1 a, Type2 b) try {
T obj(a,b);
swap(*this,obj);
return Ok;
}
catch(const Errno& e) {
return e;
};

Note: This rule is important. Real practice may want do things
slightly different in some cases, but no. Hidden and hard to
find bugs may exist (maybe the concept or interface of the
class design should be reconsidered).
Note: For reset() (no argument), object return (and Reply) state is
always default regardless of the Errno, if exists, returned.

~T Destruct object

Postcondition: object does not exist

bool is_default() const
return true iff object is equivalent to the default constructed
object (i.e a.is_default() <==> a==T(), if operator== is defined).

bool operator==(const T& rhs) const
This function returns true iff *this and rhs are not distinguishable
by using any non-private member on the same condition unless
otherwise explicitly specified.

Note: Object equivalence does not include equivalence of SLI and
address of its own.

T& operator=(const T&)
Reconstruct object to the state as the copy constructor constructed
(same as reset(const T&) except the return type and throwing error).

void swap(&T)
Exchange object state of *this and the argument indicated object.

Take 'a.swap(b)' (commutative) for instance, the state of a is set
to the state of b. The previous state of a becomes the state of b.
The same applies for b

Note: No construct and destruct semantics is involved

Reply
Class specific throw class inheriting Errno.

_.. prefix for system-specific members or candidate, or restricted..etc.

wy_.. prefix for internal members (for testing, etc.).

------

wij

unread,
Jul 9, 2023, 11:29:13 AM7/9/23
to
A section about throwing error is added to libwy guidelines, posted for review.

-------- Throwing error
As an elementary library, libwy has to make any error report of provided
functions be able properly handled by its caller to accomplish the function
requested and don't create exception.
Throwing error loses the context information. FUNCTIONS (and ctor/dtor/..) SHOULD
CATCH ALL CONFUSING ERRORS THROWN TO CONVERT TO THE ONE IT IS RESPONSIBLE.
Difficulty exists to achieve this requirement especially for template classes,
therefore, libwy should minimize using template in general (Array<T> seems to
defy this rule...).

Note: Implement should also consider other things like thread cancellation and
signal-safety.

+--------------------------+
| Appendix: Losing Context |
+--------------------------+
The error thrown may not associate to the function being called.

int read_integer(size_t maxlen) {
int v;
if(maxlen>10) {
throw Invalid_Argument;
}
// read string and convert to integer (Invalid_Argument may be thrown)
return v;
}

void f(int count) {
int v;
if(count<0) {
throw Invalid_Argument;
}
for(int i=0; i<count; ++i) {
try {
v= read_integer(8);
}
catch (Invalid_Argument) {
// May not be the Invalid_Argument in read_integer(..) thrown.
// Decisions based on this assumption will be wrong.
}
}
}

The Invalid_Argument caught in f may not mean the argument of read_integer(..)
is invalid. Likewise, if Invalid_Argument is rethrown or passed, it does not
necessarily mean the argument 'count' of f is invalid.
-----------

Paavo Helde

unread,
Jul 9, 2023, 11:44:39 AM7/9/23
to
09.07.2023 18:29 wij kirjutas:
> +--------------------------+
> | Appendix: Losing Context |
> +--------------------------+
> The error thrown may not associate to the function being called.
>
> int read_integer(size_t maxlen) {
> int v;
> if(maxlen>10) {
> throw Invalid_Argument;

I can see now what you mean by "losing context". It has never occurred
to me to throw such meaningless exceptions. Try something like this instead:

throw std::runtime_error(
"Invalid parameter maxlen=" +
std::to_string(maxlen) +
" passed to read_integer(), the value must be not larger than 10");

wij

unread,
Jul 9, 2023, 12:03:03 PM7/9/23
to
Your brain is stuffed by stdc++'s lie.
"Losing Context" means it is DOOMED in C++ language. All the user can do is
working around.

wij

unread,
Jul 9, 2023, 12:09:23 PM7/9/23
to

wij

unread,
Jul 9, 2023, 12:12:35 PM7/9/23
to
On Monday, July 10, 2023 at 12:03:03 AM UTC+8, wij wrote:
Throwing std::runtime_error is like signaling a global object is updated.

wij

unread,
Jul 9, 2023, 1:50:58 PM7/9/23
to
On Sunday, July 9, 2023 at 11:44:39 PM UTC+8, Paavo Helde wrote:
To be specific about such coding:

throw std::runtime_error(
"Invalid parameter maxlen=" +
std::to_string(maxlen) +
" passed to read_integer(), the value must be not larger than 10");

0. Error report of a function (mostly) is made for the caller to know and handle.
In this case, owing to the context loss issue, caller of a function generally
won't be able to tell it is exactly the argument 'maxlen' causing the
problem. Such coding as the above does not make things better in the general case.
1. Error report itself has to be error free.
2. Efficiency. Many of such error reports may happen and happen frequently.
No one like to do the heavy decoding job everywhere.

wij

unread,
Jul 10, 2023, 12:54:37 PM7/10/23
to
A passage is added to the appendix Losing Context, of which mostly had shown.
Also, the followings are for review:

--------------------
In a metaphor, throwing error is like using the thread local errno to propagate
the error report with the enforcement that the stack will unwind if the function
in the stack frame did not check the errno. So, in the general case, caller
function has no way to know which function in the unwound path is responsible
for the errno.
----------------

The following is the appendix for C++ programmers not used to this "disguised C++ of C" programming guidelines.
+---------------------------------------------------+
| Appendix B: Possible doubt of StdC++ library user |
+---------------------------------------------------+
.Program is difficult to read: The author believes, mixed style may be the major
issue. Programmers are more often in the code review process for correctness
than exhibiting the codes. Terseness that hides details that need to guess, and
scattered source may be more of the problem to concern.

.'Less human language': The semantics of C++ language is defined ultimately by
the target assembly and recognized by CPU, not any one, no magic in this
regard. Far too human language and ideal consume effort and creates illusion
difficult to debug. Although it is possible the high level language can lead
to the invention of a new machine, but, so far, no machine can be beyond TM.
If the language wants so, do it directly. As the author understood, parallel
(muli-cpu) machine is likely still a TM or a TM with an additional random bit
source. Again, no fancy things there (Of course, proving these statement wrong
is very welcome to all, e.g. Artificial Neural Network. This is just what libwy
should say).

---------------------------------------------------------
Finally, the author (me) thinks he is probably not able to do much more to
make libwy mature, help is needed from different person. Join the project to
modify it (or, if you can do similar things by your own, notify me the project.
Or, spread the message).

Chris M. Thomasson

unread,
Jul 10, 2023, 2:56:19 PM7/10/23
to
On 6/28/2023 1:36 AM, wij wrote:
> The following are rewrite of my 20-years old coding guide lines. I'd like to
> ask for suggestions to refine in the way because one reason is that recently
> the many questions I saw in this forum are actually rooted in class design
> problems (or problems of being too expressive, arbitrary).
[...]

Here are some coding guidelines for ya:

https://www.stroustrup.com/JSF-AV-rules.pdf

;^)

wij

unread,
Jul 10, 2023, 5:13:59 PM7/10/23
to
Thanks, it looks my class member rule win! (I can provide stronger restriction).

This is one thousand bill [$1000千圓], keep the change.

wij

unread,
Jul 11, 2023, 7:56:25 PM7/11/23
to
As I am in the mood, so Appendix C is added. Updated wording are in
https://sourceforge.net/projects/cscall/files/MisFiles/ClassGuidelines.txt/download

+--------------------------------------------+
| Appendix C: Returning 'error' is necessary |
+--------------------------------------------+
Reusing of execution codes has two kinds, as a function or as a macro (C++
template is an advanced kind in source level). The exit points (I mean the
flowchar of machine codes, or assembly) of a function are traditionally
simplified to one point (the return address) by encoding the exit information
carried in the branch point into a return object --- the basic picture of the
original codes (the nature may be more intriguing).
So, the birth of the branch point info. carrying object and the associated cost
of space and time to encode and decode. 'error' or 'exception' is what human
thinks, it is just an information/mechanism for branching.

Control-flows between modules are similar, just have more room to play tricks.

So, libwy regards returning Errno as primitive.

wij

unread,
Jul 14, 2023, 12:05:52 AM7/14/23
to
A section about ops-on-self is added to ClassGuidelines. The updated version is at
https://sourceforge.net/projects/cscall/files/MisFiles/ClassGuidelines.txt/download

------ Operations on self
Functions that modify something from source info. is what programs do.
But, when the source and destination object overlap (this is possible when the
object is indicated by pointer or reference), things become tricky, e.g.

t.reset(t);
str.reset(cstr_ptr); // cstr_ptr points to the inside string of str.

Theoretically, semantics of such expression is classified as undefined. In
C/C++, if the source object is indicated const, the promise will be broken, at
least.

[Rationale omitted]

This library decided to test this situation, if cannot work around, ELOOP is
returned.
----------

Such ops-on-self should not be few. How does std library deal with this (e.g. string, vector)?
The cost of checking is not cheap, suggestion and idea?

Chris M. Thomasson

unread,
Jul 18, 2023, 5:00:30 PM7/18/23
to
I concur. :^)

wij

unread,
Jul 20, 2023, 8:07:07 PM7/20/23
to
I think ClassGuidelines https://sourceforge.net/projects/cscall/files/MisFiles/ClassGuidelines.txt/download
should be completed. The following should be the last section added.

-------- Returning error and the error checking
Error (branching point information) not handled is always a hiden 'UB' waiting
to happen. The guideline is: ALWAYS CHECK THE ERROR. This guideline extends to
API, therefore, "__attribute__((warn_unused_result))" should be added to the
returning Errno. As usual, exception exists.

From function usage point of view: The general concern of error checking may
be the complexity and readibility of the source codes. In this guidelines'
view, without the checking and handling of the error, the evaluation
(readibility, beauty,..) of the codes is one-sided judgement, because error
checking and handling is part of the complete program.
The minimum check-and-throw pattern is better than ignoration, such coding is
equivalent to a blended 'cheap and better assert'.
Practice proves that error checking actually reduces software development and
maintenance time, significantly.

--------------
I don't know what to say. My coding guidelines are very different from MANY programs I saw.
The last one I checked is "asio C++ library" https://sourceforge.net/projects/asio/
Lots of codes are about 'concept and policy', difficult to find real ones, and it seems
to throw error. How does it expect user catch and handle it? ...
0 new messages