The worst things about C++

  • Thread starter Steven T. Hatton
  • Start date
B

bjarne

Steven said:
It was a joke: "Trust me, I did it that way. ;) "

Sorry to misread the "wink".

What I find is that reading a different introductory text usually gives me a
new perspective on things.

It is always a good idea to get two views on something.

I could not agree with you more. I know my learning style is quite
different from that of most other people. I quite enjoyed TC++PL(SE), and
plan to read it again. I consider it among the great works of the 20th
Century.
Thanks.


Though I never finished it, I rather enjoyed this as well:
http://www.hkbu.edu.hk/~ppp/cpr/toc.html

You might find his "Prolegomena" easier to start with.

-- Bjarne Stroustrup; http:/www.research.att.com/~bs
 
N

Noah Roberts

peter said:
Steven T. Hatton skrev:
I do not have that book: if you believe there to be problems with const
references in my example, please feel free to speak up.

The problem has to do with how character arrays get resolved per
reference vs. value. For example you wouldn't be able to pass two
string literals of different length to that function:

char * x = min("hello", "hell");

Nope...won't work (not according to the book anyway).

Reason, becasue it is reference semantics it attempts to pass char[6]&
as first and char[5]& as second parameters...can't happen.

The way to fix this is to override for char*, which you have to do
anyway:

template<>
char* min<char*>(char* l, char* r) { ... do strcmp stuff...}

So, there is no "problem" to speak of, just something to be aware of.
 
S

Steven T. Hatton

bjarne said:
Steven T. Hatton wrote: [...]
It is always a good idea to get two views on something.

My preference is that alternative views are mutually consistent. Especially
when they are from the same source. Perhaps the failing is in me, but I
cannot reconcile these two paragraphs from the Standard:

<quote>
§3.3/4[Note: these restrictions apply to the declarative region into which a
name is introduced, which is not necessarily the same as the region in
which the declaration occurs. In particular, elaborated-type-specifiers
(3.3.1) and friend declarations (11.4) may introduce a (possibly not
visible) name into an enclosing namespace; these restrictions apply to that
region. Local extern declarations (3.5) may introduce a name into the
declarative region where the declaration appears and also introduce a
(possibly not visible) name into an enclosing namespace; these restrictions
apply to both regions. ]

§3.3.1/6[Note: friend declarations refer to functions or classes that are
members of the nearest enclosing namespace, but they do not introduce new
names into that namespace (7.3.1.2). Function declarations at block scope
and object declarations with the extern specifier at block scope refer to
delarations[sic] that are members of an enclosing namespace, but they do
not introduce new names into that scope. ]
</quote>

I assume that if you wrote either of the above two paragraphs, it was the
latter. At least so much as `friend' and `namespace' are involved.
You might find his "Prolegomena" easier to start with.

Thanks for the pointer. It certainly looks like a good place to get an
overview of Kant's thinking.
 
B

bjarne

Steven said:
My preference is that alternative views are mutually consistent. Especially
when they are from the same source. Perhaps the failing is in me, but I
cannot reconcile these two paragraphs from the Standard:

<quote>
§3.3/4[Note: these restrictions apply to the declarative region into which a
name is introduced, which is not necessarily the same as the region in
which the declaration occurs. In particular, elaborated-type-specifiers
(3.3.1) and friend declarations (11.4) may introduce a (possibly not
visible) name into an enclosing namespace; these restrictions apply to that
region. Local extern declarations (3.5) may introduce a name into the
declarative region where the declaration appears and also introduce a
(possibly not visible) name into an enclosing namespace; these restrictions
apply to both regions. ]

§3.3.1/6[Note: friend declarations refer to functions or classes that are
members of the nearest enclosing namespace, but they do not introduce new
names into that namespace (7.3.1.2). Function declarations at block scope
and object declarations with the extern specifier at block scope refer to
delarations[sic] that are members of an enclosing namespace, but they do
not introduce new names into that scope. ]
</quote>

I assume that if you wrote either of the above two paragraphs, it was the
latter. At least so much as `friend' and `namespace' are involved.

In the old days, the names of friends were injected into the enclosings
scope. That caused a variety of problems. Not injecting made friends
useless. The solution was to make the names of friends mach
declarations in enclosing scopes but otherwise to otherwise make
functions inaccessible ("invisible"). This is hard to say briefly and
precisely.

My reading of the two paragraphs is that they use different words to
express the idea that the function names are there in the enclosing
scopes in a way so that they are not seen unless they are used just
right.

Yes, I know that what I just said is also vague, but I'm trying to
convey an idea, not to define a mechanism. Consider this simplified
example from 7.3.1.2 Namespace member definitions:

namespace A {
class X {
friend void f(X); // A::f(X) is a friend
};

// A::f is not visible here (so f is not "injected" into A)

X x;
void f(X) { /* ... */} // definition of A::f

// A::f is visible here and known to be friends (so f was "known
to be" in A or we couldn't have connected the friend declaration to the
definition)
}
 
S

Steven T. Hatton

bjarne said:
My preference is that alternative views are mutually consistent.
Especially
when they are from the same source. Perhaps the failing is in me, but I
cannot reconcile these two paragraphs from the Standard:

<quote>
§3.3/4[Note: these restrictions apply to the declarative region into
which a name is introduced, which is not necessarily the same as the
region in which the declaration occurs. In particular,
elaborated-type-specifiers (3.3.1) and friend declarations (11.4) may
introduce a (possibly not visible) name into an enclosing namespace;
these restrictions apply to that region. Local extern declarations (3.5)
may introduce a name into the declarative region where the declaration
appears and also introduce a (possibly not visible) name into an
enclosing namespace; these restrictions apply to both regions. ]

§3.3.1/6[Note: friend declarations refer to functions or classes that are
members of the nearest enclosing namespace, but they do not introduce new
names into that namespace (7.3.1.2). Function declarations at block scope
and object declarations with the extern specifier at block scope refer to
delarations[sic] that are members of an enclosing namespace, but they do
not introduce new names into that scope. ]
</quote>

I assume that if you wrote either of the above two paragraphs, it was the
latter. At least so much as `friend' and `namespace' are involved.

In the old days, the names of friends were injected into the enclosings
scope. That caused a variety of problems. Not injecting made friends
useless. The solution was to make the names of friends mach
declarations in enclosing scopes but otherwise to otherwise make
functions inaccessible ("invisible"). This is hard to say briefly and
precisely.

The biggest problem in trying to understand the intent is that terms are
being used in ways which are not consistent with definitions provided
within the Standard. It seems likely that names which are first declared
(and not defined) as friends are given a unique status, only shared by
other names introduced by non-defining friend declarations. Unfortunately
there is no special term used to denote that status. Instead terms such
as "visible" which have a specific definition (which in the case of visible
includes being the antonym of "hidden") are used in ways inconsistent with
their definitions.
My reading of the two paragraphs is that they use different words to
express the idea that the function names are there in the enclosing
scopes in a way so that they are not seen unless they are used just
right.

Yes, I know that what I just said is also vague, but I'm trying to
convey an idea, not to define a mechanism. Consider this simplified
example from 7.3.1.2 Namespace member definitions:

namespace A {
class X {
friend void f(X); // A::f(X) is a friend
};

// A::f is not visible here (so f is not "injected" into A)

X x;
void f(X) { /* ... */} // definition of A::f

// A::f is visible here and known to be friends (so f was "known
to be" in A or we couldn't have connected the friend declaration to the
definition)
}

My understanding is that a matching function signature with a different
return type will lead to an ambiguity (or other conflict) error if it is
visible in the immediate enclosing namespace of the friend declaration.
It's still not clear to me whether f() might be found through ADL, and if
so, whether it becomes a member of A. For example:

namespace A { class X; }
namespace Z {
void f(A::X);//forward declaration
}
namespace A {
class X {
friend void f(X); // should this refer to Z::f(A::X)?
};
}

namespace Z {
void f(A::X){/*define here*/}
}

namespace A {
//Can Z::f(A::X) be used here without qualification or further definition?
//If f(X) can be used here, is it a member of A?
}
 
S

Steven T. Hatton

Steven said:
bjarne said:
My preference is that alternative views are mutually consistent.
Especially
when they are from the same source. Perhaps the failing is in me, but I
cannot reconcile these two paragraphs from the Standard:

<quote>
§3.3/4[Note: these restrictions apply to the declarative region into
which a name is introduced, which is not necessarily the same as the
region in which the declaration occurs. In particular,
elaborated-type-specifiers (3.3.1) and friend declarations (11.4) may
introduce a (possibly not visible) name into an enclosing namespace;
these restrictions apply to that region. Local extern declarations (3.5)
may introduce a name into the declarative region where the declaration
appears and also introduce a (possibly not visible) name into an
enclosing namespace; these restrictions apply to both regions. ]

§3.3.1/6[Note: friend declarations refer to functions or classes that
are members of the nearest enclosing namespace, but they do not
introduce new names into that namespace (7.3.1.2). Function declarations
at block scope and object declarations with the extern specifier at
block scope refer to delarations[sic] that are members of an enclosing
namespace, but they do not introduce new names into that scope. ]
</quote>

I assume that if you wrote either of the above two paragraphs, it was
the
latter. At least so much as `friend' and `namespace' are involved.

In the old days, the names of friends were injected into the enclosings
scope. That caused a variety of problems. Not injecting made friends
useless. The solution was to make the names of friends mach
declarations in enclosing scopes but otherwise to otherwise make
functions inaccessible ("invisible"). This is hard to say briefly and
precisely.

The biggest problem in trying to understand the intent is that terms are
being used in ways which are not consistent with definitions provided
within the Standard. It seems likely that names which are first declared
(and not defined) as friends are given a unique status, only shared by
other names introduced by non-defining friend declarations. Unfortunately
there is no special term used to denote that status. Instead terms such
as "visible" which have a specific definition (which in the case of
visible includes being the antonym of "hidden") are used in ways
inconsistent with their definitions.
My reading of the two paragraphs is that they use different words to
express the idea that the function names are there in the enclosing
scopes in a way so that they are not seen unless they are used just
right.

Yes, I know that what I just said is also vague, but I'm trying to
convey an idea, not to define a mechanism. Consider this simplified
example from 7.3.1.2 Namespace member definitions:

namespace A {
class X {
friend void f(X); // A::f(X) is a friend
};

// A::f is not visible here (so f is not "injected" into A)

X x;
void f(X) { /* ... */} // definition of A::f

// A::f is visible here and known to be friends (so f was "known
to be" in A or we couldn't have connected the friend declaration to the
definition)
}

My understanding is that a matching function signature with a different
return type will lead to an ambiguity (or other conflict) error if it is
visible in the immediate enclosing namespace of the friend declaration.
It's still not clear to me whether f() might be found through ADL, and if
so, whether it becomes a member of A. For example:

namespace A { class X; }
namespace Z {
void f(A::X);//forward declaration
}
namespace A {
class X {
friend void f(X); // should this refer to Z::f(A::X)?
};
}

namespace Z {
void f(A::X){/*define here*/}
}

namespace A {
//Can Z::f(A::X) be used here without qualification or further
definition? //If f(X) can be used here, is it a member of A?
}

I should have waited until I got some sleep. I knew there was something
wrong with what I was thinking. I now recall what the error is. A
function is not looked up using ADL until it is called. I was thinking it
might be looked up when it was declared.

Now I have to go and think about how that impacts the proposed code shown
above.
 
W

wkaras

bjarne wrote:
....
(5) many people have failed to learn C++ from TC++PL
(6) many people have failed to learn C++ from other sources

Here, "many" means "hundreds of thousands".
....

I hope I'm not going to hear this quoted every day in the coffee
room for the rest of my working life.

There are alot of programmers who feel they have failed to learn
C++ because they don't know it as well as some other, smaller
language (usually C). Same programmers are often recognized
as experts in <insert-OS-of-your-choice> even though they have
only ever used around 20% of the OS's API.

Alot of other programmers decide (after reading part of or
just thumbing through a C++ book) that they do not wish to
learn C++.

In my own limited experience, I can't remember anyone
with basic aptitude as a programmer who couldn't
eventually grasp any aspect of C++ they wanted
or needed to know. So I could not say I ever meet
anyone who truely "failed" to learn C++.

I first learned (or started learning) C++ from the ARM,
which is supposed to be the worst way to do it. But
the notes about how O-O and other language features
could be implemented gave me confidence that the
language was relevant to the performance-sensitive
stuff I work on. Maybe we're relics, but alot of
programmers don't feel comfortable writing code
that they don't feel they could "manually compile"
into assembler. I haven't seen any C++ books
that do a good job of presenting contemporary C++
to programmers with this point of view.
 
W

wkaras

Signal9 said:
If you invest your time in this language it will not steer you wrong.
Honestly I would say learn this as your major language, but also learn
other languages. Java and C# are very popular right now and have a
decent mature framework.

If the point is to learn about programming (not to get a job) learn
C++, SmallTalk, one not-too-wierd assembly language, LISP
and maybe Prolog.

I'm no expert in Java or C#, but as far as I've seen, feature-wise,
they're subsets of C++.
 
P

peter koch

kwikius skrev:
Nope. they both have automated garbage collection.

regards
Andy Little

That is not a major difference. Most C++ programs could simply attach a
garbage collector and forget about memory reclamaining. That does not
mean that they are not very different languages. I actually find very
few similarities between C++ and Java except from the similar syntax.

/Peter
 
K

kwikius

peter said:
kwikius skrev:

That is not a major difference. Most C++ programs could simply attach a
garbage collector and forget about memory reclamaining.

I have looked into the Boehm garbage collector, but currently I am
using boost::shared_ptr. I'm not sure how easy it is to detect that a
type has been declared on the stack, when it must work e.g with boost
shared pointer for example. Ideally this would be a compile time error,
which is possible in C++/CLI AFAICS (iow providing the choice of
allocation strategy). To do it in C++, the only way I can see is to set
up a memory pool and state that type X must be allocated from it. I can
then examine this pointer in the ctor (typically of a base class of
some users class) and throw an exception if the pointer isnt
referencing something in the memory pool. A compile time error would be
much superior though, e.g by a keyword specifying that type X ( base
class) must be allocated on the heap. Of course it may be possible to
modify the Boehm collector to do this, but it would still be a runtime
thing AFAICS

regards
Andy Little
 
P

peter koch

kwikius skrev:
I have looked into the Boehm garbage collector, but currently I am
using boost::shared_ptr.
Watch out. Shared pointer provides for proper destruction of shared,
dynamically allocated objects. The Boehm collector allows memory to be
reused, but it does not destruct objects (and rightly so in my
opinion).
I'm not sure how easy it is to detect that a
type has been declared on the stack, when it must work e.g with boost
shared pointer for example.
I do not see the problem - so long as you assure that there are no
pointers to the stacbased object when you destruct it. I do believe you
know that you can specify your own deleter?
Ideally this would be a compile time error,
which is possible in C++/CLI AFAICS (iow providing the choice of
allocation strategy).

I do not quite get it. So far as I know, the standard C++ way is to
give you a choice: you can allocate it on the stack or you can allocate
it dynamically. If you wish to, there are techniques to prevent a class
from being allocated on the stack.
To do it in C++, the only way I can see is to set
up a memory pool and state that type X must be allocated from it. I can
then examine this pointer in the ctor (typically of a base class of
some users class) and throw an exception if the pointer isnt
referencing something in the memory pool. A compile time error would be
much superior though, e.g by a keyword specifying that type X ( base
class) must be allocated on the heap.
Typically, if you require your class to be dynamically allocated, you
declare the constructor(s) private and create a factory function.
Of course it may be possible to
modify the Boehm collector to do this, but it would still be a runtime
thing AFAICS

The Boehm collector does nothing of that sort. It simply sits there,
transparantly keeping an eye on your dynamically allocated memory,
cleaning up as needed. You do not change your code in anyway (the
restrictions are few: basically you are not allowed to "hide" any
pointers by e.g. copying them to a file, xoring them and stuff like
that).
But I might have misunderstood you ;)

/Peter
 
W

wkaras

kwikius said:
Nope. they both have automated garbage collection.

Strictly speaking, you're correct that this is feature of Java/C# that
C++ (as typically used) lacks. But my impression is, in Java/C#, it's
just a convenience (and an occasional inconvenience), it doesn't
enable a fundamentally different style or type of programming.
 
K

kwikius

Strictly speaking, you're correct that this is feature of Java/C# that
C++ (as typically used) lacks. But my impression is, in Java/C#, it's
just a convenience (and an occasional inconvenience), it doesn't
enable a fundamentally different style or type of programming.

That is arguable... Anyway, though I love C++, nevertheless there is no
point trying to make out the language is perfect and C++ can learn from
other languages too.

regards
Andy Little
 
K

kwikius

peter said:
kwikius skrev:
Watch out. Shared pointer provides for proper destruction of shared,
dynamically allocated objects. The Boehm collector allows memory to be
reused, but it does not destruct objects (and rightly so in my
opinion).
I do not see the problem - so long as you assure that there are no
pointers to the stacbased object when you destruct it. I do believe you
know that you can specify your own deleter?

I do not quite get it. So far as I know, the standard C++ way is to
give you a choice: you can allocate it on the stack or you can allocate
it dynamically. If you wish to, there are techniques to prevent a class
from being allocated on the stack.

FWIW I want to be able to allocate on the stack or the heap, but I also
wanted to be able to return a shared_ptr to an object allocated on the
stack or the heap. Originally I figured I couldnt allow heap allocation
if I allowed stack allocation and vice versa, however after
experimenting I came up with the following, tested with boost-1.33.1.
It seems to work but I'm not sure if its correct and has the
disadvantage of requiring a dummy member shared pointer in the class.
When a classs my is allocated under control of shared ptr it is
technically under control of two shared pointers and I don't know if
that is acceptable. Fails unless USE_DUMMY_SHARED_POINTER is defined

#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

// MSVC specific stuff to check memory leaks
#if defined (_MSC_VER) && defined(_DEBUG)
#include <crtdbg.h>
#endif

struct null_deleter
{
void operator()(void const*) const
{
}
};

// if defined puts a dummy shared_ptr in the class
#define USE_DUMMY_SHARED_POINTER

// class where I want to be able
// to get a shared_pointer to it
// via get_ptr function
struct my : boost::enable_shared_from_this<my>
{
#ifdef USE_DUMMY_SHARED_POINTER
typedef boost::shared_ptr<my> ptr;
#endif
ptr dummy_shared_ptr;
my(){
#ifdef USE_DUMMY_SHARED_POINTER
ptr temp(this,null_deleter());
dummy_shared_ptr = temp;
#endif
}

ptr get_ptr()
{
return shared_from_this();
}
};

int main()
{
try{
// try stack alloc
my x;

my::ptr px = x.get_ptr();

if( px.get() == &x){
std::cout << "ptr to x checks ok\n";
}
else {
std::cout << "ptr to x bad\n";
}

// try heap alloc under shared_ptr management
my::ptr x_ptr = my::ptr(new my());

my::ptr px_ptr = x_ptr->get_ptr();

if( px_ptr.get() == x_ptr.get()){
std::cout << "ptr to x_ptr checks ok\n";
}
else {
std::cout << "ptr to x_ptr bad\n";
}

std::cout << "no exceptions\n";
}
catch (std::exception & e){
std::cout << e.what() <<'\n';
}

//msvc leak checks
#if defined (_MSC_VER) && defined(_DEBUG)
#define SET_CRT_DEBUG_FIELD(a) \
_CrtSetDbgFlag((a) | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG))
#define CLEAR_CRT_DEBUG_FIELD(a)\
_CrtSetDbgFlag(~(a) & _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG))

SET_CRT_DEBUG_FIELD( _CRTDBG_LEAK_CHECK_DF );
#endif

}
 
K

kwikius

kwikius said:
struct my : boost::enable_shared_from_this<my>
{
#ifdef USE_DUMMY_SHARED_POINTER
typedef boost::shared_ptr<my> ptr;
#endif
ptr dummy_shared_ptr;
my(){
#ifdef USE_DUMMY_SHARED_POINTER
ptr temp(this,null_deleter());
dummy_shared_ptr = temp;
#endif
}

ptr get_ptr()
{
return shared_from_this();
}
};

Ooops.

Should be:

struct my : boost::enable_shared_from_this<my>
{
typedef boost::shared_ptr<my> ptr;
#ifdef USE_DUMMY_SHARED_POINTER
ptr dummy_shared_ptr;
#endif
my(){
#ifdef USE_DUMMY_SHARED_POINTER
ptr temp(this,null_deleter());
dummy_shared_ptr = temp;
#endif
}

ptr get_ptr()
{
return shared_from_this();
}
};

regards
Andy Little
 

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,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top