Similar to PR 1706, which cleans up the error codes for the filesystem
code, but done for the kernel error codes. This removes the ErrCodes
namespace and specifies the errors directly. This also fixes up any
straggling lines of code that weren't using the named error codes where
applicable.
It seems palma is done through bluetooth, we need this for pokemon go however more research needs to be done when we actually get palma working. This is presumably used for transfering data between the controller and the console, it does not seem for actual input as far as I know.
There's no real point to keeping the separate enum around, especially
given the name of the error code itself is supposed to document what the
value actually represents.
empty() in this case will always return false, since the returned
container is a std::array. Instead, check if all given users are invalid
before returning the error code.
The previous expression would copy sizeof(size_t) amount of bytes (8 on
a 64-bit platform) rather than the full 10 bytes comprising the uuid
member.
Given the source and destination types are the same, we can just use an
assignment here instead.
When yuzu is compiled in release mode this function is unused, however,
when compiled in debug mode, it's used within a LOG_TRACE statement.
This prevents erroneous compilation warnings about an unused function
(that isn't actually totally unused).
* svcBreak now dumps information from the debug buffer passed
info1 and info2 seem to somtimes hold an address to a buffer, this is usually 4 bytes or the size of the int and contains an error code. There's other circumstances where it can be something different so we hexdump these to examine them at a later date.
* Addressed comments
Started implementation of the AM message queue mainly used in state getters. Added the ability to switch docked mode whilst in game without stopping emulation. Also removed some things which shouldn't be labelled as stubs as they're implemented correctly
These are needed by Edizon to boot. They are used to see if a user is using SX OS, as SX OS registers a custom service called 'tx' and attempting to register a service of the same name lets the application know if it is present.
Allows resuing a common KeyManager when a large amount of NCAs are handled by the same class. Should the parameter not be provided, a new KeyManager will be constructed, as was the default behavior prior to this.
Previously, we would let a user enter an unbounded name and then
silently truncate away characters that went over the 32-character limit.
This is kind of bad from the UX point of view, because we're essentially
not doing what the user intended in certain scenarios.
Instead, we clamp it to 32 characters and make that visually apparent in
the dialog box to provide a name for a user.
* get rid of boost::optional
* Remove optional references
* Use std::reference_wrapper for optional references
* Fix clang format
* Fix clang format part 2
* Adressed feedback
* Fix clang format and MacOS build
Returns the raw NACP bytes and the raw icon bytes into a title-provided buffer. Pulls from Registration Cache for control data, returning all zeros should it not exist.
When enabled in settings, PatchNSO will dump the unmodified NSO that it was passed to a file named <build id>.nso in the dump root for the current title ID.
Also adds UI option in Debug > Dump section, with the idea later things to be dumped (i.e. other game data or textures, etc) will use the same group box.
An object to read SaveDataInfo objects, which describe a unique save on the system. This implementation iterates through all the directories in the save data space and uses the paths to reconstruct the metadata.
Many of the Current<Thing> getters (as well as a few others) were
missing const qualified variants, which makes it a pain to retrieve
certain things from const qualified references to System.
This is just flat data, so it doesn't really need to be in the function
itself. This also allows deduplicating the constant for the backup size
in GetImageSize().
Now that we've gotten the innaccurate error codes out of the way, we can
finally toss away a bunch of these, trimming down the error codes to
ones that are actually used and knocking out two TODO comments.
All priority checks are supposed to occur before checking the validity
of the thread handle, we're also not supposed to return
ERR_NOT_AUTHORIZED here.
We can just call the function instead of duplicating the code here. This
also prevents an unused function warning.
We also don't need to take the lambda capture by reference. It's just a
u64 value, so by value is fine here.
* Fixed conflict with nfp
* Few fixups for nfc
* Conflict 2
* Fixed AttachAvailabilityChangeEvent
* Conflict 3
* Fixed byte padding
* Refactored amiibo to not reside in "System"
* Removed remaining references of nfc from system
* used enum for Nfc GetStateOld
* Added missing newline
* Moved file operations to front end
* Conflict 4
* Amiibos now use structs and added mutexes
* Removed amiibo_path
In the kernel, there isn't a singular handle table that everything gets
tossed into or used, rather, each process gets its own handle table that
it uses. This currently isn't an issue for us, since we only execute one
process at the moment, but we may as well get this out of the way so
it's not a headache later on.
This should be comparing against the queried process' vma_map, not the
current process'. The only reason this hasn't become an issue yet is we
currently only handle one process being active at any time.
This is a subset of the better-hid-2 changes, this fixes input in various games which don't support dual joycons. This pr will search for the next best controller which is supported by the current game
This event signals the game when new DLC is purchased from the eShop while the game is running. Since, for the forseeable future, yuzu will not have this ability, it seems safe to stub with a dummy event that will never fire. This is needed to boot Sonic Mania Plus (update v1.04).
When writing VFS, it initally seemed useful to include a function to in-place convert container files into directories in one homogenous directory structure, but re-evaluating it now there have been plenty of chances to use it and there has always been a better way. Removing as it is unused and likely will not be used.
Now that the changes clarifying the address spaces has been merged, we
can wrap the checks that the kernel performs when mapping shared memory
(and other forms of memory) into its own helper function and then use
those within MapSharedMemory and UnmapSharedMemory to complete the
sanitizing checks that are supposed to be done.
swap.h only needs to be present in the header for the type aliases and
definitions, it's not actually needed in the cpp files though. input.h
is just unused entirely in xpad.h
These classes are non-trivial and are definitely going to be changed in
the future, so we default these to prevent issues with forward
declarations, and to keep the compiler from inlining tear-down code.
The constructor alone is pretty large, the reading code should be split
into its consistuent parts to make it easier to understand it without
having to build a mental model of a 300+ line function.
The only reason the getter existed was to check whether or not the
program NCA was null. Instead, we can just provide a function to query
for the existence of it, instead of exposing it entirely.
The data retrieved in these cases are ultimately chiefly owned by either
the RegisteredCache instance itself, or the filesystem factories. Both
these should live throughout the use of their contained data. If they
don't, it should be considered an interface/design issue, and using
shared_ptr instances here would mask that, as the data would always be
prolonged after the main owner's lifetime ended.
This makes the lifetime of the data explicit and makes it harder to
accidentally create cyclic references. It also makes the interface
slightly more flexible than the previous API, as a shared_ptr can be
created from a unique_ptr, but not the other way around, so this allows
for that use-case if it ever becomes necessary in some form.
There's no need for shared ownership here, as the only owning class
instance of those Cpu instances is the System class itself. We can also
make the thread_to_cpu map use regular pointers instead of shared_ptrs,
given that the Cpu instances will always outlive the cases where they're
used with that map.
Like the barrier, this is owned entirely by the System and will always
outlive the encompassing state, so shared ownership semantics aren't
necessary here.
This will always outlive the Cpu instances, since it's destroyed after
we destroy the Cpu instances on shutdown, so there's no need for shared
ownership semantics here.
This function doesn't need to care about ownership semantics, so we can
just pass it a reference to the file itself, rather than a
std::shared_ptr alias.
So, one thing that's puzzled me is why the kernel seemed to *not* use
the direct code address ranges in some cases for some service functions.
For example, in svcMapMemory, the full address space width is compared
against for validity, but for svcMapSharedMemory, it compares against
0xFFE00000, 0xFF8000000, and 0x7FF8000000 as upper bounds, and uses
either 0x200000 or 0x8000000 as the lower-bounds as the beginning of the
compared range. Coincidentally, these exact same values are also used in
svcGetInfo, and also when initializing the user address space, so this
is actually retrieving the ASLR extents, not the extents of the address
space in general.
This should help diagnose crashes easier and prevent many users thinking that a game is still running when in fact it's just an audio thread still running(this is typically not killed when svcBreak is hit since the game expects us to do this)
A fairly basic service function, which only appears to currently support
retrieving the process state. This also alters the ProcessStatus enum to
contain all of the values that a kernel process seems to be able of
reporting with regards to state.
Neither of these functions alter the ownership of the provided pointer,
so we can simply make the parameters a reference rather than a direct
shared pointer alias. This way we also disallow passing incorrect memory values like
nullptr.
We can just reserve the memory then perform successive insertions
instead of needing to use memcpy. This also avoids the need to zero out
the output vector's memory before performing the insertions.
We can also std::move the output std::vector into the destination so
that we don't need to make a completely new copy of the vector, getting
rid of an unnecessary allocation.
Additionally, we can use iterators to determine the beginning and end
ranges of the std::vector instances that comprise the output vector, as
the end of one range just becomes the beginning for the next successive
range, and since std::vector's iterator constructor copies data within
the range [begin, end), this is more straightforward and gets rid of the
need to have an offset variable that keeps getting incremented to
determine where to do the next std::memcpy.
Given it's only used in one spot and has a fairly generic name, we can
just specify it directly in the function call. This also the benefit of
automatically moving it.
Instead, we can make it part of the type and make named variables for
them, so they only require one definition (and if they ever change for
whatever reason, they only need to be changed in one spot).
Given the VirtualFile instance isn't stored into the class as a data
member, or written to, this can just be turned into a const reference,
as the constructor doesn't need to make a copy of it.
If the data is unconditionally being appended to the back of a
std::vector, we can just directly insert it there without the need to
insert all of the elements one-by-one with a std::back_inserter.
Given the filesystem should always be assumed to be volatile, we should
check and bail out if a seek operation isn't successful. This'll prevent
potentially writing/returning garbage data from the function in rare
cases.
This also allows removing a check to see if an offset is within the
bounds of a file before perfoming a seek operation. If a seek is
attempted beyond the end of a file, it will fail, so this essentially
combines two checks into one in one place.
Given the file is opened a few lines above and no operations are done,
other than check if the file is in a valid state, the read/write pointer
will always be at the beginning of the file.
These only exist to ferry data into a Process instance and end up going
out of scope quite early. Because of this, we can just make it a plain
struct for holding things and just std::move it into the relevant
function. There's no need to make this inherit from the kernel's Object
type.
Regular value initialization is adequate here for zeroing out data. It
also has the benefit of not invoking undefined behavior if a non-trivial
type is ever added to the struct for whatever reason.
Now that all external dependencies are hidden, we can remove
json-headers from the publically linked libraries, as the use of this
library is now completely hidden from external users of the web_service
library. We can also make the web_services library private as well,
considering it's not a requirement. If a library needs to link in
web_service, it should be done explicitly -- not via indirect linking.
This adds the missing address range checking that the service functions
do before attempting to map or unmap memory. Given that both service
functions perform the same set of checks in the same order, we can wrap
these into a function and just call it from both functions, which
deduplicates a little bit of code.
HandheldVariant is for specific games which expect handheld controllers to be at position 8(kirby), however this doesn't fix all games as some games require handhelds to be at position 0(snipperclips)
There's no real need to use a shared pointer in these cases, and only
makes object management more fragile in terms of how easy it would be to
introduce cycles. Instead, just do the simple thing of using a regular
pointer. Much of this is just a hold-over from citra anyways.
It also doesn't make sense from a behavioral point of view for a
process' thread to prolong the lifetime of the process itself (the
process is supposed to own the thread, not the other way around).
We don't need to potentially heap-allocate a std::string instance here,
given the data is known ahead of time. We can just place it within an
array and pass this to the mbedtls functions.
Neither of these functions require the use of shared ownership of the
returned pointer. This makes it more difficult to create reference
cycles with, and makes the interface more generic, as std::shared_ptr
instances can be created from a std::unique_ptr, but the vice-versa
isn't possible. This also alters relevant functions to take NCA
arguments by const reference rather than a const reference to a
std::shared_ptr. These functions don't alter the ownership of the memory
used by the NCA instance, so we can make the interface more generic by
not assuming anything about the type of smart pointer the NCA is
contained within and make it the caller's responsibility to ensure the
supplied NCA is valid.
We can just compare the existing std::vector instance with a constexpr
std::array containing the desired match. This is lighter resource-wise,
as we don't need to allocate on the heap.
Adds missing includes to prevent potential compilation issues in the
future. Also moves the definition of a struct into the cpp file, so that
some includes don't need to be introduced within the header.
When loading NROs, svcBreak is called to signal to the debugger that a new "module" is loaded. As no debugger is technically attached we shouldn't be killing the programs execution.
Hardware tests show that trying to unmap an unmapped buffer already should always succeed. Hardware test was tested up to 32 iterations of attempting to unmap
This was the result of a typo accidentally introduced in
e51d715700. This restores the previous
correct behavior.
The behavior with the reference was incorrect and would cause some games
to fail to boot.
Conceptually, it doesn't make sense for a thread to be able to persist
the lifetime of a scheduler. A scheduler should be taking care of the
threads; the threads should not be taking care of the scheduler.
If the threads outlive the scheduler (or we simply don't actually
terminate/shutdown the threads), then it should be considered a bug
that we need to fix.
Attributing this to balika011, as they opened #1317 to attempt to fix
this in a similar way, but my refactoring of the kernel code caused
quite a few conflicts.
Specifically bugs/crashes that arise when putting them in positions that are legal but not typical, such as midline, between patch data, or between patch records.
Many of the member variables of the thread class aren't even used
outside of the class itself, so there's no need to make those variables
public. This change follows in the steps of the previous changes that
made other kernel types' members private.
The main motivation behind this is that the Thread class will likely
change in the future as emulation becomes more accurate, and letting
random bits of the emulator access data members of the Thread class
directly makes it a pain to shuffle around and/or modify internals.
Having all data members public like this also makes it difficult to
reason about certain bits of behavior without first verifying what parts
of the core actually use them.
Everything being public also generally follows the tendency for changes
to be introduced in completely different translation units that would
otherwise be better introduced as an addition to the Thread class'
public interface.
GetName() returns a std::string by value, not by reference, so after the
std::string_view is constructed, it's not well defined to actually
execute any member functions of std::string_view that attempt to access
the data, as the std::string has already been destroyed. Instead, we can
just use a std::string and erase the last four characters.
When searching for a file extension, it's generally preferable to begin
the search at the end of the string rather than the beginning, as the
whole string isn't going to be walked just to check for something at the
end of it.
If an error occurs when constructing the PartitionFilesystem instance,
the constructor would be exited early, which wouldn't initialize the
extracted data member, making it possible for other code to perform an
uninitialized read by calling the public IsExtractedType() member
function. This prevents that.
Like the other two bits of factored out code, this can also be put
within its own function. We can also modify the code so that it accepts
a const reference to a std::vector of files, this way, we can
deduplicate the file retrieval.
Now the constructor for NSP isn't a combination of multiple behaviors in
one spot. It's nice and separate.
This too, is completely separate behavior from what is in the
constructor, so we can move this to its own isolated function to keep
everything self-contained.
If any of the error paths before the NCA retrieval are taken, it'll
result in program_nca_status being left in an inconsistent state. So we
initialize it by default with a value indicating an error.
In some games (Splatoon 2 and Splatoon 2 Splatfest World Premiere, notably), pass offset=0 and count=2047 into the ListAddOnContent method which should return all DLCs for the current title. The (presumably) intended behavior is to successfully return a empty array but because of a < v. <= in an if statement, a failure error code was returned causing these games to svcBreak. This fixes that if statement.
Now that we have all of the rearranging and proper structure sizes in
place, it's fairly trivial to implement svcGetThreadContext(). In the
64-bit case we can more or less just write out the context as is, minus
some minor value sanitizing. In the 32-bit case we'll need to clear out
the registers that wouldn't normally be accessible from a 32-bit
AArch32 exectuable (or process).
This will be necessary for the implementation of svcGetThreadContext(),
as the kernel checks whether or not the process that owns the thread
that has it context being retrieved is a 64-bit or 32-bit process.
If the process is 32-bit, then the upper 15 general-purpose registers
and upper 16 vector registers are cleared to zero (as AArch32 only has
15 GPRs and 16 128-bit vector registers. not 31 general-purpose
registers and 32 128-bit vector registers like AArch64).
Makes the public interface consistent in terms of how accesses are done
on a process object. It also makes it slightly nicer to reason about the
logic of the process class, as we don't want to expose everything to
external code.
Internally within the kernel, it also includes a member variable for the
floating-point status register, and TPIDR, so we should do the same here to match
it.
While we're at it, also fix up the size of the struct and add a static
assertion to ensure it always stays the correct size.
A process should never require being reference counted in this
situation. If the handle to a process is freed before this function is
called, it's definitely a bug with our lifetime management, so we can
put the requirement in place for the API that the process must be a
valid instance.
boost::static_pointer_cast for boost::intrusive_ptr (what SharedPtr is),
takes its parameter by const reference. Given that, it means that this
std::move doesn't actually do anything other than obscure what the
function's actual behavior is, so we can remove this. To clarify, this
would only do something if the parameter was either taking its argument
by value, by non-const ref, or by rvalue-reference.
The std::vector instances are already initially allocated with all
entries having these values, there's no need to loop through and fill
them with it again when they aren't modified.
auto x = 0;
auto-deduces x to be an int. This is undesirable when working with
unsigned values. It also causes sign conversion warnings. Instead, we
can make it a proper unsigned value with the correct width that the
following expressions operate on.
Given these are only added to the class to allow those functions to
access the private constructor, it's a better approach to just make them
static functions in the interface, to make the dependency explicit.
This converts it into a regular constructor parameter. There's no need
to make this a template parameter on the class when it functions
perfectly well as a constructor argument.
This also reduces the amount of code bloat produced by the compiler, as
it doesn't need to generate the same code for multiple different
instantiations of the same class type, but with a different fill value.
The locations of these can actually vary depending on the address space
layout, so we shouldn't be using these when determining where to map
memory or be using them as offsets for calculations. This keeps all the
memory ranges flexible and malleable based off of the virtual memory
manager instance state.
Previously, these were reporting hardcoded values, but given the regions
can change depending on the requested address spaces, these need to
report the values that the memory manager contains.
Rather than hard-code the address range to be 36-bit, we can derive the
parameters from supplied NPDM metadata if the supplied exectuable
supports it. This is the bare minimum necessary for this to be possible.
The following commits will rework the memory code further to adjust to
this.
* Implemented fatal:u properly
fatal:u now is properly implemented with all the ipc cmds. Error reports/Crash reports are also now implemented for fatal:u. Crash reports save to yuzu/logs/crash_reports/
The register dump is currently known as sysmodules send all zeros. If there are any non zero values for the "registers" or the unknown values, let me know!
* Fatal:U fixups
* Made fatal:u execution break more clear
* Fatal fixups
* Stubbed IRS
Currently we have no ideal way of implementing IRS. For the time being we should have the functions stubbed until we come up with a way to emulate IRS properly.
* Added IRS to logging backend
* Forward declared shared memory for irs
Preserves the meaning/type-safetiness of the stream state instead of
making it an opaque u32. This makes it usable for other things outside
of the service HLE context.
Even though setting this value to 3 is more correct. We break more games than we fix due to missing implementations. We should keep this as 0 for the time being
The owning process of a thread is required to exist before the thread,
so we can enforce this API-wise by using a reference. We can also avoid
the reliance on the system instance by using that parameter to access
the page table that needs to be set.
Several classes have a lot of non-trivial members within them, or don't
but likely should have the destructor defaulted in the cpp file for
future-proofing/being more friendly to forward declarations.
Leaving the destructor unspecified allows the compiler to inline the
destruction code all over the place, which is generally undesirable from
a code bloat perspective.
This was used in two different translation units
(deconstructed_rom_directory and patch_manager). This means we'd be
pointlessly duplicating the whole array twice due to it being defined
within the header.
These variables aren't used, which still has an impact, as std::vector
cannot be optimized away by the compiler (it's constructor and
destructor are both non-trivial), so this was just wasting memory.
std::shared_ptr isn't strictly necessary here and is only ever used in
contexts where the object doesn't depend on being shared. This also
makes the interface more flexible, as it's possible to create a
std::shared_ptr from a std::unique_ptr (std::shared_ptr has a
constructor that accepts a std::unique_ptr), but not the other way
around.
An instance of the NAX apploader already has an existing NAX instance in
memory. Calling directly into IdentifyType() directly would re-parse the
whole file again into yet another NAX instance, only to toss it away
again.
This gets rid of unnecessary/redundant file parsing and allocations.
AsNCA() allocates an NCA instance every time it's called. In the current
manner it's used, it's quite inefficient as it's making a redundant
allocation.
We can just amend the order of the conditionals to make it easier to
just call it once.
* Reworked incorrect nifm stubs
Need confirmation on `CreateTemporaryNetworkProfile`, unsure which game uses it but according to reversing. It should return a uuid which we currently don't do.
Any 0 client id is considered an invalid client id.
GetRequestState 0 is considered invalid.
* Fixups for nifm
* Fix bug where default username value for yuzu_cmd create an userprofile with uninitialize data as username
* Fix format
* Apply code review changes
* Remove nullptr check
This can just be a regular function, getting rid of the need to also
explicitly undef the define at the end of the file. Given FuncReturn()
was already converted into a function, it's #undef can also be removed.
Previously the second half of the value being written would overwrite
the first half. Thankfully this wasn't a bug that was being encountered,
as the function is currently unused.
This modifies the CPU interface to more accurately match an
AArch64-supporting CPU as opposed to an ARM11 one. Two of the methods
don't even make sense to keep around for this interface, as Adv Simd is
used, rather than the VFP in the primary execution state. This is
essentially a modernization change that should have occurred from the
get-go.
The kernel does the equivalent of the following check before proceeding:
if (address + 0x8000000000 < 0x7FFFE00000) {
return ERR_INVALID_MEMORY_STATE;
}
which is essentially what our IsKernelVirtualAddress() function does. So
we should also be checking for this.
The kernel also checks if the given input addresses are 4-byte aligned,
however our Mutex::TryAcquire() and Mutex::Release() functions already
handle this, so we don't need to add code for this case.
Courtesy of @ogniK5377.
This also moves them into the cpp file and limits the visibility to
where they're directly used. It also gets rid of unused or duplicate
error codes.
The kernel caps the size limit of shared memory to 8589930496 bytes (or
(1GB - 512 bytes) * 8), so approximately 8GB, where every GB has a 512
byte sector taken off of it.
It also ensures the shared memory is created with either read or
read/write permissions for both permission types passed in, allowing the
remote permissions to also be set as "don't care".
Part of the checking done by the kernel is to check if the given
address and size are 4KB aligned, as well as checking if the size isn't
zero. It also only allows mapping shared memory as readable or
read/write, but nothing else, and so we shouldn't allow mapping as
anything else either.
Previously, these were sitting outside of the Kernel namespace, which
doesn't really make sense, given they're related to the Thread class
which is within the Kernel namespace.
There were a few places where nested namespace specifiers weren't being
used where they could be within the service code. This amends that to
make the namespacing a tiny bit more compact.
While unlikely, it does avoid constructing a std::string and
unnecessarily calling into the memory code if a game or executable
decides to be really silly about their logging.
This places the font data within cpp files, which mitigates the
possibility of the font data being duplicated within the binary if it's
referred to in more than one translation unit in the future. It also
stores the data within a std::array, which is more flexible when it
comes to operating with the standard library.
Furthermore, it makes the data arrays const. This is what we want, as it
allows the compiler to store the data within the read-only segment. As
it is, having several large sections of mutable data like this just
leaves spots in memory that we can accidentally write to (via accidental
overruns, what have you) and actually have it work. This ensures the
font data remains the same no matter what.
When a destructor isn't defaulted into a cpp file, it can cause the use
of forward declarations to seemingly fail to compile for non-obvious
reasons. It also allows inlining of the construction/destruction logic
all over the place where a constructor or destructor is invoked, which
can lead to code bloat. This isn't so much a worry here, given the
services won't be created and destroyed frequently.
The cause of the above mentioned non-obvious errors can be demonstrated
as follows:
------- Demonstrative example, if you know how the described error happens, skip forwards -------
Assume we have the following in the header, which we'll call "thing.h":
\#include <memory>
// Forward declaration. For example purposes, assume the definition
// of Object is in some header named "object.h"
class Object;
class Thing {
public:
// assume no constructors or destructors are specified here,
// or the constructors/destructors are defined as:
//
// Thing() = default;
// ~Thing() = default;
//
// ... Some interface member functions would be defined here
private:
std::shared_ptr<Object> obj;
};
If this header is included in a cpp file, (which we'll call "main.cpp"),
this will result in a compilation error, because even though no
destructor is specified, the destructor will still need to be generated by
the compiler because std::shared_ptr's destructor is *not* trivial (in
other words, it does something other than nothing), as std::shared_ptr's
destructor needs to do two things:
1. Decrement the shared reference count of the object being pointed to,
and if the reference count decrements to zero,
2. Free the Object instance's memory (aka deallocate the memory it's
pointing to).
And so the compiler generates the code for the destructor doing this inside main.cpp.
Now, keep in mind, the Object forward declaration is not a complete type. All it
does is tell the compiler "a type named Object exists" and allows us to
use the name in certain situations to avoid a header dependency. So the
compiler needs to generate destruction code for Object, but the compiler
doesn't know *how* to destruct it. A forward declaration doesn't tell
the compiler anything about Object's constructor or destructor. So, the
compiler will issue an error in this case because it's undefined
behavior to try and deallocate (or construct) an incomplete type and
std::shared_ptr and std::unique_ptr make sure this isn't the case
internally.
Now, if we had defaulted the destructor in "thing.cpp", where we also
include "object.h", this would never be an issue, as the destructor
would only have its code generated in one place, and it would be in a
place where the full class definition of Object would be visible to the
compiler.
---------------------- End example ----------------------------
Given these service classes are more than certainly going to change in
the future, this defaults the constructors and destructors into the
relevant cpp files to make the construction and destruction of all of
the services consistent and unlikely to run into cases where forward
declarations are indirectly causing compilation errors. It also has the
plus of avoiding the need to rebuild several services if destruction
logic changes, since it would only be necessary to recompile the single
cpp file.