26

I am comparing C++ with Python. It is clear that C++ is much more efficient and that the C++ code compiles directly to machine code whereas in Python it is interpreted.

I do understand that Python is a higher-level language. But what difference does it make? I intuitively understand that C++ offers more "control", but what does it mean concretely? Can you give an example of things you can do with C++, but not with Python?

17
  • 10
    Write some programs with them. We could tell you but the answers won't necessarily make much sense and you'll likely learn it faster just by solving some problems with both. Oct 31 '20 at 19:00
  • 2
    Intuitive is all there is. As the answer below shows, the difference has more to do with common scenarios and conventional usage rather than a principled distinction. The short version is that the only absolute difference is that C++ and Python make different tradeoffs about what they make easy vs. what they make hard. Nov 1 '20 at 11:37
  • 12
    "It is clear that C++ is much more efficient" -- Define "efficient"? I'm not sure but I think the only technically accurate meaning of that word has to do with the ratio of usable output power to the input power, and while that's an important property of, say, a motor, it doesn't really mean anything in a programming context. Whereas the things that are important here, for a programming language, are much less straightforward to define or measure. Saying "X is clearly more efficient than Y" without defining what you mean just sounds like soapboxing your personal opinion.
    – ilkkachu
    Nov 1 '20 at 19:54
  • 2
    Write programs with both is not really a good answer. The question is how do they differ specifically?
    – wada
    Nov 2 '20 at 11:16
  • 7
    @ilkkachu - in the context of this question it is VERY clear it's referring to efficiency of CPU and memory usage. Nov 2 '20 at 17:15
67

Can you give an example of things you can do with C++ but not with python.

Sure. For instance, C++ gives you control over where objects are placed in memory. The programmer decides whether an object is stored on the stack or the heap - and can even control where on the heap by using a custom allocator. This can be helpful when exploiting memory locality effects to improve memory access performance.

Also, in C++, you control when an object is destroyed, which allows side effects to be attached to that destruction. For instance, if you have a C++ object for an open file, you control when this object is destroyed, allowing the destructor of that object to automatically and promptly release the native file handle. In Python, you have no control when the object is freed, and therefore have to close the file manually.

In C++, you can also perform crazy optimizations by manipulating pointers. I remember one memorable case where a program had to store a great many object references representing boolean functions, some of which were negated. Rather than storing the negation in a separate variable, they stored it in the least significant bit of the pointer, which was known to always be 0 due to memory alignment. This allowed them to cut their memory use in half. They couldn't have done this in Python.

It is clear that C++ is much more efficient

Not necessarily: That the programmer has this control doesn't necessarily imply he will use that control better than Python does. After all, the guys that write Python runtimes are quite skilled software developers, and probably know more about low level performance optimization than the average C++ programmer.

So if you are choosing between C++ and Python, it is true that C++ gives you more control - but it is also true that C++ demands that control. You must manage memory. You must ensure you never use after free. And so on. Are the benefits of having that control worth spending the time to exercise it? Or would you rather have the language runtime take care of these details, so you can focus on other things? The answer will depend on what kind of software you are writing.

30
  • 11
    Python also only has 1 type of int and float. So I suppose C++ has more "control" over bytes in a number. Oct 31 '20 at 21:56
  • 28
    If you write "modern C++", you rarely do any manual resource-management. But you can, and therefore are always near enough to the chainsaw to cut your head off. Oct 31 '20 at 22:48
  • 21
    "After all, the guys that write Python runtimes are quite skilled software developers, and probably know more about low level performance optimization than the average C++ programmer." The dominent python runtime is a bytecode inrepreter with no jit and thus fundamentally slow. Even the jit-based runtimes suffer from the highly dynamic nature of the language. Nov 1 '20 at 0:38
  • 11
    I think this is a pretty good answer, but you make it sound like there's a pretty good chance if an "average" programmer writes a program in C++ and Python, the Python one might be faster. While you're not wrong C++ isn't necessarily faster, if we're playing the odds I'll certainly bet it is. Even if the programmer is just "average". And this is because the canonical CPython implementation, despite the knowledge of the people that wrote it, is terribly inefficient.
    – Phil Frost
    Nov 1 '20 at 20:46
  • 6
    In Python, you have no control when the object is freed, and therefore have to close the file manually. That's just not true. Python has extra syntax for that, with open(filename,'r') as file: which has the same destructor semantics as C++.
    – nwp
    Nov 2 '20 at 10:11
51

It is clear that C++ is much more efficient and that the C++ code compiles directly to machine code whereas in Python it is interpreted.

This is wrong. C++ and Python are programming languages. A programming language is an abstract set of mathematical rules and restrictions. It is neither compiled nor interpreted. It just is.

Any language can be implemented by an interpreter. Any language can be implemented by a compiler. Many languages have both compiled and interpreted implementations. Many modern high-performance language implementations use both interpretation and compilation in the same implementation.

For example, every single currently existing Python implementation uses a compiler:

  • CPython has an ahead-of-time compiler that compiles Python source code to CPython byte code.
  • PyPy has an ahead-of-time compiler that compiles Python source code to PyPy byte code, then it has another compiler that compiles PyPy byte code to native machine code (or ECMAScript when running in a browser).
  • IronPython has an ahead-of-time compiler that compiles Python source code to DLR Trees, then it has another compiler that compiles DLT Trees to .NET CIL byte code, which the .NET runtime then may or may not compile to native machine code.
  • GraalPython has an ahead-of-time compiler that compiles Python source code to Truffle ASTs, then Truffle takes over and compiles Truffle ASTs to JVM byte code or native machine code, in the first case, the JVM may then in turn compile the JVM byte code to native machine code.

So, three out of four will eventually compile to native machine code, and even the odd one out still has a compiler.

On the other hand, there are interpreters for C++ such as CINT and Ch. And there is Cling, which is an interpreter based on a JIT compiler based on the Clang ahead-of-time compiler …

I intuitively understand that C++ offers more "control" but what does it mean concretely?

How do you define "control"?

Some people claim that C++ is "closer to the hardware" or that it allows you to have "fine-grained control over how things are laid out in memory". This is not actually true, however. C++ is specified in terms of an Abstract Machine. There is nothing in the specification guaranteeing that this Abstract Machine corresponds to the real machine that the program is running.

There are C++ compilers targeting ECMAScript and the JVM. How is a C++ program running inside a JavaScript interpreter inside a JVM inside a VirtualBox VM "close to the hardware" and a Python program running on a microcontroller that doesn't even have an OS is not? C++ also has some rules about how implementors are allowed to optimize memory layout, which means that the actual memory layout may not be what you think it is.

And when you say "closer to the machine", then what "machine" are you talking about? C++ may be close to a PDP-11, but it is most definitely not close to a Reduceron, and Python is closer than C++ to an Azul Vega 3.

Some people say that C++ has more "control" because all of its default abstractions are "zero overhead", and any abstractions that are not zero overhead are opt-in. But that is not true either: for example, you cannot opt out of pointers, they are always there. I have worked on a machine that has no pointers, and both C and C++ are very slow on that particular machine, because they have to effectively run inside an interpreter that simulates pointers, and since pointers are most heavily used in high-performance code, that has a huge impact.

Instead of pointers, the machine has a concept of object references in the CPU itself. Unfortunately, there is no native Python implementation on this machine, but there is a native JVM, and on that particular machine, Java was significantly faster than C or C++.

Now, you might say all of those are weird niche machines, surely C++ is close to the PCs we are all actually using? Well, I would argue that this is only technically true, but not for the reason you think it is.

C++ is not "close to the machine" because of anything in C++. Rather, it is "close to the machine" because CPU vendors like Intel and AMD are working very hard to make their CPUs work the way C and C++ expects them to. So, in some sense C++ is not close to the machine, rather the CPU vendors are making the machine close to C++.

Can you give an example of things you can do with C++ but not with python.

I can't, because there is no such thing.

Both languages are Turing-complete, meaning you can compute any computable function on the natural numbers.

Both languages are "Tetris-complete", meaning you can process user input, interact with the environment, interact with the OS, interact with libraries written in C or other languages.

And yes, there has been an experimental Operating System written in Python.

45
  • 39
    "C++ is not "close to the machine" because of anything in C++. Rather, it is "close to the machine" because CPU vendors like Intel and AMD are working very hard to make their CPUs work the way C and C++ expects them to." Since those processors predate C++, wouldn't it make more sense to say that C was designed to mimic certain processors, and that the success of those processors encouraged the proliferation of particular assembly tropes that prioritize design that is appropriate for C? Oct 31 '20 at 16:02
  • 26
    A programming language is an abstract set of mathematical rules and restrictions. I agree with this in theory. In practice many languages don't even have de jure standards (eg Python). Of those that have standards, programs are often written against implementation instead of the standard (eg Haskell vs GHC). A extreme minority of commonly used languages have well-defined mathematical semantics (only SML comes to mind). Oct 31 '20 at 18:50
  • 37
    About Turing Completness and being able to do "anything" - while you can compute anything, you cannot do anything in language that is TC. For example I am able to compute anything using BF, but I am not able to send TCP request without external help. It is important to differentiate between these concepts.
    – Hauleth
    Oct 31 '20 at 19:42
  • 21
    "I can't, because there is no such thing." - There most definitely is, if you don't argue the semantics/theoretical nature, but look at the practical problem. When people say "Python" they usually don't mean the set of the rules that define the grammar, but one or a few of the main implementations. You'd be hard pressed to write, day, real-time OS in any of them.
    – Dan M.
    Oct 31 '20 at 21:03
  • 28
    Similarly, the argument about Turing completeness is somewhat pointless in reality. It only says that you can implement any abstract algorithm, but if you requirements are about interacting with OS APIs or underlying HW in some specific way it just doesn't make sense.
    – Dan M.
    Oct 31 '20 at 21:06
15

I really like the existing answers separating language from implementation. That's very precise. Yet I tend to be obsessed with the practical differences, or at least I've been forced to with the teams I've worked with. I've had to be that guy, and it's not always so fun.

And at least from that practical perspective, C++ is a more explicit language. More explicitness usually translates in practice to more control. For example, C++ is statically-typed (explicitly typed), while Python is dynamically-typed (implicitly typed).

That sort of explicitness tends to translate not only to more control but more practical control so far. In theory, a more implicit language could eliminate the runtime overhead that is typically associated via exhaustive static analysis, but that tends to never happen in practice (at least among popular implementations of interpreters and compilers of languages). It's extremely difficult to do, and such an implementation would likely take longer to compile than even most C++ compilers which already tend to be notorious for their build times. So typically in practice a lot of that implicitness translates to more machine code which imposes costs at runtime, whether it is with dynamic (implicit) types or implicit memory management via GC. And that's why I'd say, so far, that the explicitness of a language like C++ tends to translate to more control (not theoretical, but practical) over the resulting machine code with most implementations over a more implicit language like Python.

Of course, more control, even if it's only in practice and not theoretical, isn't always a good thing. Giving humans more control also tends to open up more room for errors. Control can also be used for nefarious purposes, like implementing malware.

Edit: Can you give an example of things you can do with C++ but not with python.

To keep the answer simple, I would suggest for any language to look at your standard library or framework. Is it all implemented with the same language? I tend to think digging into the answer to that question will reveal the language's limitations or lack thereof, especially when you look at something like standard Python modules implemented in C for reasons other than performance. There are many cases with higher-level languages where the language is incapable, with its constructs, of talking so directly to hardware or the underlying operating system.

Edit: on auto in C++.

I got a very nice question about auto and was tempted to write a verbose explanation with code in the comments, so probably better to address that here. Immediately I would point out that:

auto x = expression;

... in C++ is not comparable in terms of implicitness/explicitness to:

x = expression;

... in Python, if we look beyond the similarity of the syntax and the requirements of auto for that expression's resulting type to be 100% unambiguous at compile-time (which means the human C++ programmer/communicator must be sufficiently explicit in the expression to the extent of total disambiguation). In Python, that expression on the right-hand side is allowed at the level of language specification to be completely ambiguous to the compiler with respect to what type x will have after compiling it (which generally translates, in practice, to more machine code that must be executed at runtime to evaluate the resulting data type). The type of x along with the expression in Python's case could vary based on runtime inputs like the contents of a file that should only be loaded at runtime, or what button the user clicks in a GUI, or types into a command prompt. With Python, we can do things like:

def func(runtime_input):
    # Return a string or an integer based on runtime input.
    if runtime_input:
        return "hello"
    else:
        return 123;

... where even a single caller can provide different values of runtime_input based on, say, what a user types into a command prompt at runtime. Python allows that degree of implicitness to the point of complete language-level ambiguity of evaluating expression types that cannot possibly be resolved, and disambiguated, at the time the program is compiled. And that's the type of implicitness/ambiguity vs. explicitness/disambiguation we find between dynamically-typed vs. statically-typed languages that I think matters most at the language level and compiler design level when we're talking about practical human control over machine code and runtime behavior. Ambiguity generally translates, in practice, to the compiler having to insert more machine code (such as additional runtime branches) and having to make more assumptions on the part of the lack of explicitness of the human communicator.

With C++, we even have keywords like sizeof and alignof and decltype whose mere presence in the language means that every single expression written in the language requires sufficient explicitness on the part of the human programmer, and sufficient information provided to the compiler, that it can always evaluate the size, alignment, and resulting data type of any valid expression written in the language before the program is executed without a single exception to the rule. No matter how deeply we recurse into templates and uses of auto, we are never allowed that level of implicitness where the compiler is unable to deduce the type unambiguously before runtime. That heavy requirement on explicitness is generally going to translate, in relative (not absolute) terms, to relatively increased control on the part of the human over the resulting runtime behavior over a language that doesn't require such explicitness.

Implicit vs. Explicit

I should clarify in this answer that when I'm talking about explicitness vs. implicitness, it might get confusing given how we think in English or any other human language. But that's completely irrelevant. This question made me think like a compiler (sometimes brilliant but also sometimes dumb, as anyone who has looked at disassembly output from their profilers will discover -- we compilers are autistic savants able to determine that your bitwise loop is basically a popcnt only to trip over our untied shoelaces and take 5 minutes to retie them while confusing ourselves). And that's my reference point. I'm an idiot/genius compiler (and anyone who has spent enough time profiling and diassembling their compiler's output will realize they are an odd mix of genius and special education material). And I'm talking like a compiler when I say what this human person is implicit about this (ambiguous) and explicit about that (unambiguous). As humans we can say, "Oh there, it's obvious I meant that," or "Or here, it's obvious what they mean." It's very easy to think we're being sufficiently explicit as humans, but I'm a dumb compiler, and I don't use unsupervised machine learning to optimally compile your code. And it's not always so obvious unless you really told me what you mean and required to do so by your language. Because otherwise, I sort of need to guess, idn't it? And you might not have much control over what I guess for you. At the very least I might guess correctly towards the outputs you wanted but maybe not towards the instructions. Do you want more control over me? Pick a language that tells me more explicitly what to do, even if that's tedious, and makes me guess/assume less. But be careful what you wish for. Or don't wish for it, and the majority of people might be better off for it.

14
  • One important difference between C++ and Python is not only that typing is static vs dynamic, but that types themselves are static in C++ and dynamic in Python. You can have static types and implicit typing, and that would allow ahead-of-time complete type deduction. With dynamic types (that is, types that can be changed throughout runtime), complete ahead-of-time type deduction is equivalent to solving the halting problem.
    – Extrarius
    Nov 1 '20 at 0:42
  • Your phrase "statically-typed (explicitly typed)" isn't quite clear to me. How would you characterize C++'s auto foo = "foo", Java's var foo = "foo", Standard ML's val foo = "foo", etc., all of which use type inference to give foo a static type that doesn't appear explicitly in the source code?
    – ruakh
    Nov 1 '20 at 7:09
  • 3
    @rhakh statically typed means the type is known at compile time (and this is true of things like auto or var, it just takes a bit more working out; its still explicitly in the source code, just not right next to the variable). Whereas in a dynamically typed language it isnt. In fact (if things like duck typing are involved) it may be completely different types each time a method called. The same variable may hold a string on tuesday, a number on Wednesday and a complex object on thursday (although more likely; 2 different objects that both happen to have a foo() method) Nov 1 '20 at 15:38
  • 3
    @RichardTingle I think it's incorrect to associate static typing with explicit typing and dynamic typing with implicit typing, even just in practice. For example, it's my understanding that perl is both dynamically and explicitly typed. Types are only checked at runtime, but also specified in source (by symbols, not names). However, Haskell is statically typed, and can be fully implicitly typed. Types are checked at compile time, but don't need to be specified in the source; they are implied.
    – Vaelus
    Nov 1 '20 at 19:17
  • 1
    @DemonCode en.wikipedia.org/wiki/Tagged_union
    – ojs
    Nov 2 '20 at 3:34
8

Language comparisons are always source of heated discussion and painful confusions. Giving "more control" does not mean anything by itself:

  • Both languages have very similar control flows.
  • Python being a dynamic language, it allows to change control at runtime and even trick the inheritance tree: is it more control because C++ doesn't allow it? Or is it less control, because the execution flow is free to follow some unexpected path?

In you comparison, use clear and precise wording to describe what you really want to compare. If with "more control" you mean in fact "more predictability", i.e. when you need to avoid surprises like when controlling a nuclear power-plant or a fighter jet, then C++ will give you more control, since the language definition allows more compile-time check, which reduces unexpected situations at runtime.

4

Mostly it's not the language itself but the implementations.

For embedded programming where the ultimate control is required, there is absolutely nothing that requires processors to have registers mapped to memory addresses, and C++ standard is pretty explicit that memory space does not need to be flat and while pointers can be converted to integers and back, the conversion can be arbitrary. In practice microcontrollers often do have memory mapped registers and one can access them from C++ just by casting the address as integer to pointer. For the same access in most languages usually considered high level, you'd need to implement an extension that would typically (but not necessarily) be implemented in C or assembly and called over foreign function interface that uses C calling convention.

For memory layout, the standard leaves a lot implementation-defined but in practice there are only a few well known ways that implementations do actually use. Cross-platform and cross-compiler portability can be tedious, but often in the cases where you need this control you're stuck with single HW and toolchain anyway.

Compiling to assembly isn't required by standard, as seen with Bitcode, and inline assembly is likewise a common non-standard exception. However, sometimes everything is not neatly memory-mapped and you need platform-specific instructions or compiler can't figure out the exact instructions you need for the most optimized algorithm implementation. In these cases simple integration with assembly is a huge benefit. With simple in-order non-superscalar processors just looking at the generated assembly is a useful tool for understanding what the program does at low level.

Finally, something that is a part of C++ standard and what was uncommon for languages that have objects as familiar from C++, Java, Python and others, is the control over object lifetimes and the ability to acquire and release other resources together with the memory (or more generally, have side effects to releasing the object). This allows more explicit handling of resources than garbage collection where resources are released eventually, and requires less work from client code than for example IDisposable in C# or Python context management. In practice modern C++ can handle almost all lifetime management, but if you really need to roll your own, it is possible.

The other feature in C++ standard that is unique among commonly used object languages is customizable memory allocation. Placement new allows you to skip default allocator for any object, and standard template library allows you to use custom allocator for anything. This, together with some common non-standard features, is useful for a range of things from placing higher level objects over memory mapped registers to implementing optimized memory layout and low overhead allocators.

6
  • But for embedded there are non-standard extensions, right? For example, 67 = 129; means write 129 to the raw (low) memory address 67 - typically for changing hardware registers or using memory-mapped I/O (normally assigning something to a constant would result in a compile error). That is, it is completely outside of any C++ standard (and C standard for that matter). Nov 2 '20 at 2:34
  • 3
    Why would one need non-standard language extension, and why that particular choice? You can already do it in standard C++: *reinterpret_cast<unsigned char*>(67) = 129u;. The only non-standard part is how does the compiler implement converting an integer that hasn't been cast from pointer into pointer.
    – ojs
    Nov 2 '20 at 3:18
  • 1
    Of course normally you'd cast to volatile unsigned char* to make sure the store happened exactly once because the very act of storing is a visible side effect. Now that C++ has a thread-aware memory model and std::atomic, MMIO is basically the only real use-case for volatile, other than sometimes in micro-benchmarking, or roll-your-own atomics in a few existing codebases. Nov 2 '20 at 9:33
  • Good point about volatile. I left it out because the comment was about direct access to raw memory location.
    – ojs
    Nov 2 '20 at 9:38
  • +1 Was tempted to go into memory management as well. I got all hung up on dynamic typing maybe because I recently implemented an interpreter for a DSEL where I considered dynamic typing heavily, but it just seemed so difficult to optimize away the extra layer of branching without the programmer specifying what types are involved.
    – user377672
    Nov 2 '20 at 9:53
0

In C or C++, the Standard defines the behavior of something like:

int read_int(int *p) { return *p; }

as "If p is a pointer to an object whose effective type (for C) or dynamic type (for C++) is int, then return the value of that int object. Otherwise behave in any fashion the implementation sees fit.

Many implementations however, at least if suitably configured, would define the behavior as "use the platform's natural method to perform an int-sized load from the address given in p, interpret the bit pattern as an int, and return it", without making any effort to distinguish situations where the effective/dynamic type is int (meaning the Standard would require that behavior) versus those where the Standard wouldn't require such behavior but they see fit to behave in such fashion anyway.

The amount of "control" offered by a particular C or C++ implementation depends on the extent to which the designers see fit to reliably process non-portable constructs in a documented manner characteristic of the environment. Neither language requires implementations to give programmers much control, but most implementations can be configured to do so, at least with optimizations disabled, and quality implementations can do so even with useful optimizations enabled.

10
  • 1
    Defining UB doesn't give the programmer control, it just enables "programming to the implementation instead of the interface" by poking random things until it seems to work. Also, it is a strait-jacket for the compiler, eliminating many chances for optimization, thus appreciably slowing correct programs. Of course, you can always disable optimization and there are generally other options to make different UBs defined. Nov 2 '20 at 17:33
  • 1
    What do you mean "seems to work"? The C Standard uses the term Undefined Behavior to actions which some or even most implementations should define, but may not be practical for all implementations to define in all cases. According to the authors, UB among other things "...identifies areas of possible conforming language extension: the implementor may augment the language by providing a definition of the officially undefined behavior."
    – supercat
    Nov 2 '20 at 17:40
  • As for being a strait-jacket, that is only true of compilers whose authors refuse to recognize that the Standard was intended to say that compilers need only presume that seemingly-unrelated objects might alias if they are of the same type, not that they should be willfully blind to relationships between objects. Suggesting that a compiler that sees a T* converted to a U* and can't understand everything that's done with it should allow for the possibility that any T which the pointer might target could be accessed in ways it doesn't understand between the time of the conversion and...
    – supercat
    Nov 2 '20 at 17:48
  • Yes, the standard mentions that UB is where an implementation can define things if it has good reason. But it really only should if the cost is outweight by the benefit of defining that small specific bit that specific way, not just because "I hate UB". Nov 2 '20 at 17:48
  • ...the next use of the storage as a T that is either in the context of the conversion or a surrounding context, is hardly a "strait jacket", since such conversions would generally be rare outside cases where such allowance would be necessary.
    – supercat
    Nov 2 '20 at 17:49

Not the answer you're looking for? Browse other questions tagged or ask your own question.