C++ Review Questions

Pages: 123456... 10
seeplus wrote:
(M_PI example) compiles fine with VS.

Not for me it doesn't, I get an error. "M_PI is an undeclared identifier". C2065. https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2065

VS pops up an E0020 errors as well, a rewrite of C2065. "identifier "M_PI" is undeclared". Same error, reworded.

#include <corecrt_math_defines.h> instead of <cmath>/<math.h> and there's no need to have the #define. M_PI is "there" now.

Long story short, I don't need the C library M_PI since C++20 now has pi as one of the mathematical constants. Or I can continue to use a simple 7 digit accurate constant: const double pi { 355.0/113.0 };.
^^ I have never really understood doing that.
you memorize 6 digits to get 7, similar to how in grade school they make you memorize 22/7 to get 3 digits. Why not just memorize 3 or 7 digits of pi? Its cool, but it seems like a lot more trouble than just knowing the value.
QUESTION 6)

Where is the stack & heap? Is the stack on cpu cache and heap on RAM, or are they both on RAM? Why would the stack be limited in memory space?
______________________________________________________
QUESTION 7)

My book did very simple & small float/double comparisons and they worked out nicely. Outside of my book then I come to find out that relational operations can be a problem because of precision. What method do you guys use to set the precision and compare float/double..what is the best/safest way to make comparisons?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
	//Can you use condition with floats????
	float value2 = 4.5;

	if (value2 == 4.5)			//outputs "Equals"
		cout << "Equals\n";
	else
		cout << "NOT Equals\n";
	cout << fixed << setprecision(10) << value2 << endl; //4.5000000000 == 4.5


	float value3 = 1.1;

	if (value3 == 1.1)			//outputs "NOT Equals"
		cout << "Equals\n";
	else
		cout << "NOT Equals\n";

	cout << fixed << setprecision(10) << value3 << endl; //1.1000000238 > 1.1

}

______________________________________________________
QUESTION 8)

cout << myString << flush;

Flush has not been mentioned in my book, but in this video the guy mentions that it is flushing the buffer and that things can accumulate in buffer. So I am thinking how is that possible, if the code does not issue a \n, which would have flushed the buffer or if there is no one on external keyboard hitting enter. Then it just accumulates & you need to flush at some point and this might happen with large DB with huge data?

I am thinking you guys are seeing flushes in code, if no CR/CRLF sent,which then requires a flush as an alternative?
Mr Z wrote:
Is the stack on cpu cache and heap on RAM, or are they both on RAM?

Both are stored in RAM.

The CPU cache essentially just an hardware optimization. The RAM is slow compared to how fast the CPU is so to avoid that the CPU should have to wait doing nothing while data is being transferred between the CPU and RAM the CPU uses layers of smaller faster memory "caches" where it stores portions of memory that it is currently working with (recently used/likely to soon be used). Eventually the changes will get flushed to RAM. This is somewhat more complicated because it has to deal with each CPU (core) having its own cache layer(s), etc.

Mr Z wrote:
Why would the stack be limited in memory space?

Historically this might have been necessary because the number of addresses where more limited and the stack always grows (like a stack) in one direction so it needs the addresses next to each other. It's not like the heap that can have things stored all over the place. So I think they just reserved some area and used that for the stack and the rest they used for the heap (and other things).

Nowadays with 64-bit addresses and virtual memory there isn't really that much that forces the size of the stack to be limited (except for the amount of physical memory that your computer has). At least on Linux you can set the stack size to "unlimited". The downside of this is that if you accidentally write an infinite recursion then you will end up using up all RAM memory in like a second and instead of crashing the program your computer starts running very slow because it starts swapping to disk. Not sure how bad this is with modern SSD drives but with old HDD drives it can make your whole computer very slow and it can be hard to even close down the problematic program. That's my experience.

Mr Z wrote:
What method do you guys use to set the precision and compare float/double..what is the best/safest way to make comparisons?

I think most important:
1. Avoid comparing floating points for equality.
2. Expect values and calculations with floating point numbers to have rounding errors.

Mr Z wrote:
Flush has not been mentioned in my book...

The buffer is essentially also just an "optimisation". Writing to the destination might be slow, especially when writing to files. Instead of writing every character directly to the destination it's often more efficient to write a lot of characters at once in a larger chunk. That's what the buffer is used for. It stores data that has not been written yet. There are many situations when the the buffer will get flushed automatically. E.g. if the buffer is full, std::cout will be flushed automatically when you use std::cin. Automatic flushing at the end of lines for std::cout can be turned off by calling std::ios::sync_with_stdio(false);
My book did very simple & small float/double comparisons and they worked out nicely. Outside of my book then I come to find out that relational operations can be a problem because of precision. What method do you guys use to set the precision and compare float/double..what is the best/safest way to make comparisons?

Floating point math is full of tricks and traps. The best way is to avoid testing equality at all.

If you have no choice, it's common to test for equality by comparing the absolute difference against an absolute epsilon:
std::fabs(y - x) < 0.1
The potential issue is that quantization error is relative to the magnitude of the values involved, but 0.1 doesn't account for the magnitude of either value.

From https://cplusplus.com/forum/beginner/263444/#msg1136946 :
mbozzi wrote:
[Double] precision floating point numbers in [2^52, 2^53) can only represent integers. And numbers in [2^53, 2^54) can only represent even integers. It doesn't make any sense to test whether values of those magnitudes are within a tenth of each other, for instance [...] the epsilon needs to be a relative value expressed in terms of units in the last place

See https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon for an example of how to compare for "equality" within a problem-specific relative error. Also read the linked thread when you have time.

Last edited on
stack ...
in the old days, and to some extent now, a windows computer worked more or less like this (oversimplified)...
- the program launches and in very old OS (dos, for sure, maybe in 95/98 as well?) it had one of a fixed number of memory models (which included a max stack size for each). I think they ditched that but the program still has a fixed amount of memory that it gets when it launches; you can poke this number to some extent in the compiler settings.
- that number can't change. Its the 'image' of the executable, it has a program code section, and a variable/memory section, and the program stack, and some other junk in it but its a fixed size.

then after launching you can ask for more (pointers, dynamic memory) if needed.

there are probably good reasons why it was done that way in the 80s and 90s, but I don't know them. Its probably sufficient to answer the question by saying its because that is how the major OSes work (fixed sized startup of the program image).
The stack itself being a fixed size is a performance gain: like C array vs a c++ vector -- there is overhead and overhead was the enemy in the old days.
re 6)

https://www.geeksforgeeks.org/memory-layout-of-c-program/
https://www.geeksforgeeks.org/stack-vs-heap-memory-allocation/
https://www.learncpp.com/cpp-tutorial/the-stack-and-the-heap/
...plus loads of other...

it had one of a fixed number of memory models


PS For those interested, there's some articles available:
https://devblogs.microsoft.com/oldnewthing/20200728-00/?p=104012
https://retrocomputing.stackexchange.com/questions/23974/what-were-the-actual-memory-model-definitions-in-ms-dos

This concept of near and far is where Windows terms like LPSTR, PSTR etc came from. LPSTR is Long Pointer to string which is when a far pointer would be used. PSTR is a near pointer. Nowadays these are the same.
Last edited on
Thanks guys, very informative as usual. But there is just sooo much & it never seems to end. Just when you think you are somewhere, you realize just how far, far, far away you really are.

With the machine epsilon & similar algorithms, do you guys just memorize the formula or do you treat it as reference. Or do you just know how it works & just have it in a template ready to use whenever needed, but do not memorize?

std::fabs(x-y) <= std::numeric_limits<T>::epsilon() * std::fabs(x+y) * ulp
// unless the result is subnormal
|| std::fabs(x-y) < std::numeric_limits<T>::min();
there is just sooo much & it never seems to end.

I doubt you will ever use all of what C++ has to offer, especially when using 3rd party libraries.

C++ is a moving target, just learn what you can at your own pace.

And do ask questions, after trying to understand a new concept first.

I've been self-teaching myself C++ since before C++98 was official. ANSI/ISO C++ was the "it" for C++ when I started this crazy hobby, along with other various "flavors". C++ compilers were virtually hand-crafted from scratch back then.

One of the first books I purchased, probably the first I just don't remember, published in 1996, "Sams Teach Yourself ANSI C++ in 21 Days, Premier Edition".

Here's the code for the first C++ program in the book, pg. 14:
1
2
3
4
5
6
#include <iostream.h>

void main()
{
	cout << "Hello World!\n";
}

Ick, that code is really slimy and stinky.

When C++11 was finalized, creating what is arguably labeled as "Modern C++", trying to wrap my brain around C++ became easier. Still difficult, but the additions to C++ with containers and the <random> library helped.

I prefer to USE what C++ adds to the toolbox instead of relying on C or recreating (badly) a feature like a dynamic array.
With the machine epsilon & similar algorithms, do you guys just memorize the formula or do you treat it as reference. Or do you just know how it works & just have it in a template ready to use whenever needed, but do not memorize?

For me it's like normal with formulas, I know it exists & what it does, and maybe I understand how to derive it. I try to avoid memorizing stuff by rote.

The formula solves some issues, but it's not foolproof. Overall when it comes to doing accurate floating point math, the best decisions are probably problem-specific and that's a matter for a numerical analyst.
Last edited on
> But there is just sooo much & it never seems to end.

Even for the professional programmer, it is impossible to first learn a whole programming language and then try to use it. A programming language is learned in part by trying out its facilities for small examples. Consequently, we always learn a language by mastering a series of subsets. The real question is not ‘‘Should I learn a subset first?’’ but ‘‘Which subset should I learn first?’’

For programming novices, learning the programming language should support the learning of effective programming techniques. ... The emphasis for both novices and experienced programmers should be concepts and techniques. The syntactic and semantic details of C++ are secondary to an understanding of design and programming techniques that C++ supports. ... Focussing on language-technical details can be fun, but it is not effective education.

- Stroustrup (May 1999) https://www.stroustrup.com/new_learning.pdf
Thanks for the insight.
QUESTION 9)
I always wondered about this one from my book too. I know what "this" is (&object), but does the compiler actually send "Talk(this, "Bla bla");" in that EXACT format, or does it just send
this->Talk("Bla bla");

When I tried "Talk(this, "Bla bla");" exactly like that...just to see what happens, it does not work...too many arguments. Perhaps it can only be sent in that format by the compiler.

Here is an exact copy from my Sam's book:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Human
{
private:
void Talk (string Statement)
{
cout << Statement;
}
public:
void IntroduceSelf()
{
Talk("Bla bla"); // same as Talk(this, "Bla Bla")
}
};



"What you see here is the method IntroduceSelf() using private member Talk()
to print a statement on the screen. In reality, the compiler embeds the this pointer in
calling Talk, that is invoked as Talk(this, "Bla bla")."
Last edited on
> does the compiler actually send "Talk(this, "Bla bla");" in that EXACT format,
> or does it just send this->Talk("Bla bla");

The this pointer is made available to the function; how this is done is up to the implementation. (Unlike other named parameters, this is a prvalue expression). Usually, it is made available through a register.


> When I tried "Talk(this, "Bla bla");" exactly like that...just to see what happens, it does not work

C++23:
A non-static member function can be declared to take as its first parameter an explicit object parameter, denoted with the prefixed keyword this. Once we elevate the object parameter to a proper function parameter, it can be deduced following normal function template deduction rules.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html

Try this with the Microsoft compiler (current version, with standard set to c++latest):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <string>
#include <iostream>

struct foo
{
	std::string& overloaded_mem_fun()& { std::cout << "non-const lvalue: overloaded_mem_fun() &\n" ; return str ;  }
	const std::string& overloaded_mem_fun() const& { std::cout << "const lvalue: overloaded_mem_fun() const&\n" ; return str ;  }
	std::string overloaded_mem_fun()&& { std::cout << "rvalue: overloaded_mem_fun() &&\n" ; return std::move(str) ; }

	template < typename SELF >
	decltype(auto) deduced_this_mem_fun( this SELF&& this_foo ) // three-in-one
	{
		std::cout << "deduced_this_mem_fun => " ;
		return std::forward<SELF>(this_foo).overloaded_mem_fun() ; // perfect forwarding 
	}

	std::string str ;
};

int main()
{
	foo f ; f.deduced_this_mem_fun() ; // non-const lvalue
	const foo f2 ; f2.deduced_this_mem_fun() ; // const lvalue
	foo{}.deduced_this_mem_fun() ; // prvalue
}
A lot of formal education junk is just that: junk.
I can't recall the last time I gave a rat's rear about the stack and the heap. But those 75 year old professors will blather on about it for 2-3 days or more as if it were the most important concept ever thunk up. It does not matter where your data lives: about all you need to know about this stuff is that large amounts of data should move to the heap, which is going to be done FOR you via c++ containers when using THIS language.
It does not matter where your data lives


Unless you're doing low level stuff - which few do, and if you're messing about with that type of stuff then you should know what you're doing. Or if you're doing an OS/Compiler course...

Yes, ordinarily you don't really need to know. All you really need to know is about dynamic memory at some point (for new/delete/shallow-deep copying) after you're learnt about managed pointers.

First learn C++ as C++ - then start on the underlying areas like memory etc.

I'm still astounded that in 2022, C++ is still taught in places as if it's just 'c with a few bits added on'. Ahhhhhhhh............
Last edited on
jonnin wrote:
It does not matter where your data lives

I'm going to have to beg for an indulgence to disagree slightly. Courteously. :)

It does matter where my data lives, though letting C++ take care of it from cradle to grave is for most purposes a wise use of resources vs. doing the error prone means of manually managing the data's memory.

Knowing how to do the manual two-step shuffle of memory management beyond the bare minimum basics* is an intermediate/advanced concept. Especially for someone who is tasked with dealing with some legacy code.

*exactly how "bare minimum basics" should be defined (and taught) is a loose concept. Too often a "from step one" beginner's C++ course concentrates on C/outdated C++ ways and means of "how to do things." C++ hasn't been just a "bit better C" for quite some time.
A basic understanding of storage durations (at least the three beginner ones: static, automatic and dynamic) and how this affects the lifetime of objects is absolutely essential to any C++ programmer.

Digging into the implementation details (stack, heap etc.) can wait for a while; I guess it is sufficient if a beginner at the end of an introductory programming course knows this much:

The two most fundamental resources in a computer are time (to execute instructions) and space (memory to hold data and code). In C++, there are three ways to allocate memory to hold data:
• Static memory: allocated by the linker and persisting as long as the program runs
• Stack (automatic) memory: allocated when we call a function and freed when we return from the function
• Dynamic (heap) memory: allocated by new and freed for possible reuse by delete

Static memory poses no special problem: all is taken care of before the program starts to run

Stack memory can be a problem because it is possible to use too much of it, but this is not hard to take care of.

Dynamic memory allocation: Allocation is not predictable; that is, it is not guaranteed to be a constant time operation. Usually, it is not: in many implementations of new, the time needed to allocate a new object can increase dramatically after many objects have been allocated and deallocated.
The free store may fragment; that is, after allocating and deallocating objects the remaining unused memory may be “fragmented” into a lot of little “holes” of unused space that are useless because each hole is too small to hold an object of the kind used by the application.

- extracted from Stroustrup's PP&P
Thanks for everyone's input as the information is invaluable. My second book has provided me with a wealth of additional information that has not been covered in my first book. Things such as NAN (Not a Number) and infinity deductions & how floating point numbers of different types (mixed expressions) are handled and converted.

I was oblivious to this & never knew this was happening behind the scenes. There is so much more info in my 2nd book Beginning C++20 that I find myself reading it slower to try and absorb and retain as much info. Even the std::format I have not seen before and it is great to come across it and have it covered this way.
Agreed, a little basic overview of it is useful. I was just trying to say that an overwhelmed beginner can table the stack/heap thing after a quick overview and circle back to it if needed later.
Legacy code or not, even back when I was writing what we call legacy code now (early 90s).. I can't remember caring until the stack blew out, which you then fix.
Last edited on
Pages: 123456... 10